您的位置:首页 > 其它

安卓基础:自定义控件实现ViewPager指示器

2016-03-20 16:22 351 查看
花了一天时间学了下Android自定义控件实现ViewPagerIndicator,ViewPagerIndicator相信大家大家都做过,特别是APP主页面的时候,一般情况下都会使用第三方的开源框架去进行实现,下面就是我自己实现的一个框架,可以更深入的了解其内部的机制。

下面我将简单介绍一下这个实例,如下图,内容区域为一个ViewPager,当滑动ViewPager的时候,顶部三角形和亮度指示器会跟随我们的手指进行移动,上面的tab也支持点击



-

- 接下来附上代码,首先是布局文件

- 这里是自定义的tab属性

<?xml version="1.0" encoding="utf-8"?>
<resources>

<!--自定义属性,format是值的类型,-->
<attr name="visible_tab_count" format="integer"/>
<!--定义标签-->
<declare-styleable name="ViewPagerIndicator">
<attr name="visible_tab_count"/>
</declare-styleable>
</resources>


这里是布局文件,其中tab选项可以在com.hwy.view.ViewPagerIndicator中自定义view,不过这里没用

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#fff"
xmlns:hyman="http://schemas.android.com/apk/res/com.ceshi.hwy.myviewpager"
android:orientation="vertical"
>
<!--上面xmlns:hyman="http://schemas.android.com/apk/res/com.ceshi.hwy.myviewpager"
是声明attr自定义属性的明明空间
-->
<com.hwy.view.ViewPagerIndicator
android:id="@+id/id_indicator"
android:layout_width="match_parent"
android:layout_height="45dp"
android:background="@mipmap/title_bar_bg_row"
android:orientation="horizontal"
hyman:visible_tab_count="4">
<!--hyman:visible_tab_count="4" 是使用自定义 attr的属性用来表示显示4个tab>

</com.hwy.view.ViewPagerIndicator>

<android.support.v4.view.ViewPager
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:id="@+id/id_viewpager">

</android.support.v4.view.ViewPager>

</LinearLayout>


接下来是ViewPagerIndicator

package com.hwy.view;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.CornerPathEffect;
import android.graphics.Paint;
import android.graphics.Path;
import android.support.v4.view.ViewPager;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.Gravity;
import android.view.View;
import android.view.WindowManager;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.ceshi.hwy.myviewpager.R;
import java.util.List;

/**
* 创建布局文件继承LinearLayout
*/
public class ViewPagerIndicator extends LinearLayout{

private Paint mPaint;//画笔
private Path mPath;//通过Path构造一个三角形
private int mTriangleWidth;//三角形宽度
private int mTriangleheight;//三角形高度
private static final float RADIO_TRIANGLE_WIDTH = 1/6F;//三角形宽度和tab的比例
private int mInitTranslationX;//初始化的偏移位置
private int mTranslationX;//移动时的位置

private int mTabVisiableCount;//可见tab的数量
private static final int COUNT_DEFAULT_TAB = 4;//创建默认显示的tab数量的一个常量

private List<String> mTitles;//用于存放传过来Titles
private static final int COLOR_TEXT_NORMAL = 0x77ffffff;//设置一个正常的颜色
private static final int COLOR_TEXT_HIGHLIGHT = 0xffffffff;//设置一个高亮的颜色

private ViewPager mViewpager;//设置一个关联的ViewPager

public ViewPagerIndicator(Context context) {
this(context, null);
}

public ViewPagerIndicator(Context context, AttributeSet attrs) {
super(context, attrs);

//获取可见tab的数量
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ViewPagerIndicator);
mTabVisiableCount = a.getInt(R.styleable.ViewPagerIndicator_visible_tab_count
,COUNT_DEFAULT_TAB);
//如果传入一个小于0的数
if(mTabVisiableCount<0){
mTabVisiableCount = COUNT_DEFAULT_TAB;
}
a.recycle();//获取完成之后进行释放

//初始化画笔
mPaint = new Paint();
mPaint.setAntiAlias(true);//抗锯齿
mPaint.setColor(Color.parseColor("#ffffffff"));//设置画笔的颜色
mPaint.setStyle(Paint.Style.FILL);//设置画笔的风格
mPaint.setPathEffect(new CornerPathEffect(3));//设置一个圆角的效果
}

/**
* 在dispatchDraw中绘制三角形
*/
@Override
protected void dispatchDraw(Canvas canvas) {
canvas.save();
canvas.translate(mInitTranslationX + mTranslationX, getHeight() + 2);
canvas.drawPath(mPath, mPaint);//绘制
canvas.restore();
super.dispatchDraw(canvas);
}

/**
* 当view的大小发生变化时触发
*/
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);

mTriangleWidth = (int) (w / mTabVisiableCount * RADIO_TRIANGLE_WIDTH);
mInitTranslationX = w / mTabVisiableCount / 2 -mTriangleWidth / 2;//初始时的偏移量
initTrangle();//初始化三角形
}

/**
* 当xml加载完成之后会回调这个方法,可以在这个方法里面判断子view的个数
* 通过mTabvisiableCount去设置他们的宽度,显示4个则必须为屏幕宽度的1/4
*/
@Override
protected void onFinishInflate() {
super.onFinishInflate();
//拿到子元素的一个个数
int cCount = getChildCount();
if(cCount == 0){
return;
}
for(int i = 0; i < cCount; i++){
View view = getChildAt(i);
LinearLayout.LayoutParams lp = (LayoutParams) view.getLayoutParams();
lp.weight = 0;
lp.width = getScreenWidth()/mTabVisiableCount;
view.setLayoutParams(lp);
}
setItemClickEvent();
}

/**
* 获得屏幕的宽度
*/
private int getScreenWidth(){
WindowManager wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
DisplayMetrics outMetrics = new DisplayMetrics();
wm.getDefaultDisplay().getMetrics(outMetrics);
return outMetrics.widthPixels;
}

/**
* 初始化三角形
*/
private void initTrangle() {

mTriangleheight = mTriangleWidth/2;
mPath = new Path();
mPath.moveTo(0, 0);
mPath.lineTo(mTriangleWidth, 0);
mPath.lineTo(mTriangleWidth / 2, -mTriangleheight);
mPath.close();
}

//指示器和容器跟随手指进行滚动
public void scroll(int position, float offset) {
int tabWidth = getWidth()/mTabVisiableCount;//获取到每一个tab的宽度
mTranslationX = (int) (tabWidth*(offset+position));//设置x轴的偏移量

//容器移动,当tab处于移动至最后一个时
if(position >= (mTabVisiableCount-2) && offset > 0 && getChildCount() > mTabVisiableCount){
/**
* scrollTo(int x,int y);
* 如果偏移位置发生了改变,就会给mScrollX和mScrollY赋新值,改变当前位置。
* 注意:x,y代表的不是坐标点,而是偏移量。
* 例如:
* 我要移动view到坐标点(100,100),那么我的偏移量就是(0,,0)  - (100,100) = (-100 ,-100)  ,我就要执行view.scrollTo(-100,-100),达到这个效果。
*/
if(mTabVisiableCount != 1){
this.scrollTo((position -(mTabVisiableCount-2))*tabWidth + (int)(tabWidth*offset),0);
}else{
this.scrollTo(position*tabWidth+(int)(tabWidth*offset),0);
}

}
invalidate();//进行重绘
}

/**
* 动态的生成tab,不用布局文件
*/
public void setTabItemTitle(List<String> titles){
//检查title的有效性
if(titles != null && titles.size() > 0){
this.removeAllViews();//无视布局文件里设置的tab
mTitles = titles;
for (String title:mTitles) {
addView(gennerateTextView(title));
}
setItemClickEvent();
}
}

/**
* 设置可见tab的数量
*/
public void setVisiableTabCount(int count){
mTabVisiableCount = count;
}

/**
* 根据title创建tab
*/
private View gennerateTextView(String title) {
TextView tv = new TextView(getContext());
//设置布局文件LinearLayout
LinearLayout.LayoutParams lp = new LayoutParams(
LayoutParams.MATCH_PARENT,LayoutParams.MATCH_PARENT);
lp.width = getScreenWidth()/mTabVisiableCount;
tv.setText(title);
tv.setGravity(Gravity.CENTER);
tv.setTextColor(COLOR_TEXT_NORMAL);
tv.setLayoutParams(lp);
return tv;
}

/**
*重置tab的文本颜色
*/
private void resetTextViewColor(){
for(int i = 0; i < getChildCount(); i++){
View view = getChildAt(i);
if(view instanceof TextView){
((TextView) view).setTextColor(COLOR_TEXT_NORMAL);
}
}
}

/**
* 高亮某个tab的文本
*/
public void highLightTextView(int pos){
resetTextViewColor();
View view = getChildAt(pos);
if(view instanceof TextView){
((TextView) view).setTextColor(COLOR_TEXT_HIGHLIGHT);
}
}

/**
* 设置关联的Viewpager
*/
public void setViewPager(ViewPager viewPager){
mViewpager = viewPager;
}

/**
* 设置tab的点击事件
*/
public void setItemClickEvent(){
int cCount = getChildCount();
for(int i = 0;i < cCount; i++){
final int j = i;
final View view = getChildAt(i);
view.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
mViewpager.setCurrentItem(j);
}
});
}
}
}


接着是Fragment

package com.ceshi.hwy.myviewpager;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

/**
* 编写我们的Fragment
*/
public class VpSimpleFragment extends Fragment {

private String mTitle;
public static final String BUNDLE_TITLE = "title";

//在onCreateView方法中取出来
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {

//首先拿到bundle;
Bundle bundle = getArguments();
if(bundle != null){
mTitle = bundle.getString(BUNDLE_TITLE);
}
//使用一个TextView作为布局文件
TextView tv = new TextView(getActivity());
tv.setText(mTitle);
tv.setGravity(Gravity.CENTER);
return tv;
}

//通过newInstance方法创建Fragment,来传递参数
public static VpSimpleFragment newInstance(String title){
Bundle bundle = new Bundle();
bundle.putString(BUNDLE_TITLE,title);
VpSimpleFragment fragment = new VpSimpleFragment();
fragment.setArguments(bundle);
return fragment;
}

}


最后是MainActivity

package com.ceshi.hwy.myviewpager;

import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentPagerAdapter;
import android.support.v4.view.ViewPager;
import android.os.Bundle;
import android.view.Window;
import com.hwy.view.ViewPagerIndicator;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class MainActivity extends FragmentActivity {

private ViewPager mViewPager;
private ViewPagerIndicator mIndicator;

private List<String> mTitles = Arrays.asList("短信1","收藏2","推荐3","短信4",
"收藏5","推荐6","短信7","收藏8","推荐9");
private List<VpSimpleFragment> mContents = new ArrayList<VpSimpleFragment>();
//mContent相当于ViewPager的一个数据集,故我们的ViewPager还需要一个Adapter
private FragmentPagerAdapter mAdapter;

@Override
protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_main2);

initViews();
initDatas();
mIndicator.setViewPager(mViewPager);
mIndicator.setVisiableTabCount(5);
mIndicator.setTabItemTitle(mTitles);
mIndicator.highLightTextView(0);//默认显示第一个tab高亮

mViewPager.setAdapter(mAdapter);

mViewPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
//当页面在滑动的时候会调用此方法,在滑动被停止之前,此方法回一直得到
//调用。其中三个参数的含义分别为:
// :当前页面,及你点击滑动的页面
//:当前页面偏移的百分比
//:当前页面偏移的像素位置
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
//总的偏移量为:tabWidth * positionOffset + position * tabWidth
mIndicator.scroll(position,positionOffset);
}

@Override
//此方法是页面跳转完后得到调用,position是你当前选中的页面的Position(位置编号)。
public void onPageSelected(int position) {
mIndicator.highLightTextView(position);
}
//此方法是在状态改变的时候调用,其中state这个参数
//有三种状态(0,1,2)。arg0 ==1的时辰默示正在滑动,arg0==2的时辰默示滑动完毕了,arg0==0的时辰默示什么都没做。
// 当页面开始滑动的时候,三种状态的变化顺序为(1,2,0),
@Override
public void onPageScrollStateChanged(int state) {

}
});
}

//初始化数据
private void initDatas() {
for (String title: mTitles) {
VpSimpleFragment fragment = VpSimpleFragment.newInstance(title);
mContents.add(fragment);
}

mAdapter = new FragmentPagerAdapter(getSupportFragmentManager()) {
@Override
public Fragment getItem(int position) {
return mContents.get(position);
}

@Override
public int getCount() {
return mContents.size();
}
};
}

private void initViews() {
mViewPager = (ViewPager) findViewById(R.id.id_viewpager);
mIndicator = (ViewPagerIndicator) findViewById(R.id.id_indicator);
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: