安卓基础:自定义控件实现ViewPager指示器
2016-03-20 16:22
351 查看
花了一天时间学了下Android自定义控件实现ViewPagerIndicator,ViewPagerIndicator相信大家大家都做过,特别是APP主页面的时候,一般情况下都会使用第三方的开源框架去进行实现,下面就是我自己实现的一个框架,可以更深入的了解其内部的机制。
下面我将简单介绍一下这个实例,如下图,内容区域为一个ViewPager,当滑动ViewPager的时候,顶部三角形和亮度指示器会跟随我们的手指进行移动,上面的tab也支持点击
-
- 接下来附上代码,首先是布局文件
- 这里是自定义的tab属性
这里是布局文件,其中tab选项可以在com.hwy.view.ViewPagerIndicator中自定义view,不过这里没用
接下来是ViewPagerIndicator
接着是Fragment
最后是MainActivity
下面我将简单介绍一下这个实例,如下图,内容区域为一个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); } }
相关文章推荐
- Servlet 工作原理解析
- File类通过递归列出目录的结构
- 二维图形的矩阵变换(一)——基本概念
- 关于css中的opacity
- 《Java高级程序设计》第二次作业
- Android手机内存
- BZOJ-1879 Bill的挑战 状态压缩DP
- 5-22 龟兔赛跑 (20分) C语言版
- npm install失败解决方法
- linux php imagick 安装
- BZOJ-1879 Bill的挑战 状态压缩DP
- Java垃圾收集算法
- Java基础-强引用、弱引用、软引用、虚引用
- gcd 证明
- Android Material Design(6) CircularReveal圆形扩散动画的使用
- poj 2823 滑动窗口 单调队列
- Android学习第二篇——ActionBar
- hdu2068 RPG的错排(错排)
- MATLAB中conv2卷积使用方法
- JVM-对象的存活与死亡