转载请注意:http://blog.youkuaiyun.com/wjzj000/article/details/53646358
本菜开源的一个自己写的Demo,希望能给Androider们有所帮助,水平有限,见谅见谅…
https://github.com/zhiaixinyang/PersonalCollect (拆解GitHub上的优秀框架于一体,全部拆离不含任何额外的库导入)
https://github.com/zhiaixinyang/MyFirstApp(Retrofit+RxJava+MVP)
基本的自定义ViewPager的指示器
当然关于ViewPager指示器,如果只需要简洁大方,那么我们最简单的方案就是使用TabLayout+ViewPager。
当然咱们也有很多非常不错的开源框架可以选择。
本次的记录的内容就是为了自己对其中涉及的内容进行几率和梳理。关于本次:基础自定义+开源框架分析
一、基础的自定义部分
这个效果的代码思路来自于鸿洋大神的博客还有他在慕课上的视频。感觉看博客蛋疼菊紧可以去慕课看洋神的视频。
先让我们看一下效果
思路梳理
主体分为俩个部分:ViewPager+指示器。ViewPager没啥好说的该咋霍霍咋霍霍。
指示器:需要我们自己去自定义。既然是横向的,那就继承与LinearLayout,然后动态增加里边的内容(Tab)。关于那个可以移动的小三角形,我们将使用Canvas进行绘制,它的移动将和ViewPager进行相关。通过addOnPageChangeListener()监听。
代码分析
先让我们看一看添加动态Tab
//这里仿照TabLayout的写法。设置全部Tab的标题
public void setTitles(List<String> titles){
//先移除内部的全部子View(虽然现阶段根本啥也没有...)
removeAllViews();
//默认排列方式是居中的
setGravity(Gravity.LEFT);
if (titles!=null) {
for (String title : titles) {
TextView textView = new TextView(MyApplication.getAllContext());
textView.setText(title);
LinearLayout.LayoutParams lp=new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
/**
* 注意getScreenWidth是获取屏幕宽度的自定义的方法。
* 此处为什么要自己写一个获取屏幕宽度的方式,please往下看。
*/
lp.width= (int) (getScreenWidth()/3);
textView.setTextColor(Color.WHITE);
textView.setGravity(Gravity.CENTER);
textView.setTextSize(TypedValue.COMPLEX_UNIT_SP,14);
addView(textView,lp);
}
setTabClickEvent();
}
}
private float getScreenWidth(){
DisplayMetrics displayMetrics = new DisplayMetrics();
WindowManager windowManager = (WindowManager) getContext()
.getSystemService(Context.WINDOW_SERVICE);
windowManager.getDefaultDisplay().getMetrics(displayMetrics);
return displayMetrics.widthPixels;
}
这里解释为什么调用系统的获取屏幕的宽度。
先让我们看一张图
这是个方法的回调顺序。让我们一点点看:
- 构造方法第一个被调用这个没啥好说咱们跳过….
- 正式第一个被回调的onFinishInflate():当我们的布局中的XML加载完毕后,这个方法回调。此时通过getWidth()获取的控件的宽度是为0。
- 第二第三个方法,是我们自己设置的。写在View初始化之后。但是我们要注意!他们先于View的绘制系列方法之前被调用。因此此时通过getWidth()获取的控件的宽度是为0。所以我们要使用系统的方式在此处获取屏幕宽高来动态的给Tab赋值。
-
- 紧接着我们可以看到onMeasure()被调用俩次,这里没啥有价值的东西。核心是onSizeChanged()方法别调用后,这里的getWidth()即可得到控件的宽高!然后onMeasure()onLayout()onDarw()被执行。父View完成绘制。
- 最后一个回调dispatchDraw():此方法用于绘制父控件中的子控件。
那么为什么要使用系统的方式获取宽高这个问题便有了答案。
接下来让我们看一看画三角形
这里真的没有什么难的,大家这自信心上一定不要打怵,不难!
首先是先关的初始操作
//初始化画笔 paint=new Paint(); paint.setColor(Color.WHITE); //抗锯齿开启 paint.setAntiAlias(true); //传入的此类,会使画笔的线段拐角有圆角效果 paint.setPathEffect(new CornerPathEffect(3)); paint.setStyle(Paint.Style.FILL); //绘制三角形,就是三条线段(Path)按自己想要的长度围成的闭合图形,然后通过Canvas.drawPath完成绘制。 //此方法在每次View尺寸发生变化时回调。因此在此处我们可以拿到这个控件的宽和高 @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); triangleWidth=w/18;//三角形的宽(设置为屏幕宽度的十八分之一) //三角形的高,此处我直接用的宽的一半。也就是说我们绘制的是一个直角三角形 triangleHeight=triangleWidth/2; //开始时三角形的初始偏移量。(就是第一个Tab宽度的一半然后减去半个三角形的宽。) initLenght=w/6-triangleHeight/2; width=w; height=h; //通过Path路径来画三角形 path=new Path(); //首先移动到(0,0) path.moveTo(0,0); //然后从(0,0)画到(三角形宽度,0)这个点 path.lineTo(triangleWidth,0); /** *然后从(triangleWidth,0)画到三角形的定点(triangleWidth/2,-triangleHeight) * 因为我们的(0,0)是起点,所以顶点的y在(0,0)之上,根据安卓的特性所以顶点y为负 */ path.lineTo(triangleWidth/2,-triangleHeight); //闭合路径 path.close(); }
此时绘制三角形的画笔和路径都已经存在,接下来就是Canvas绘制了!
如果对Canvas类不是很了解,可以参考我的另一篇博客:
http://blog.youkuaiyun.com/wjzj000/article/details/53589024
//此方法会在父View画子View的时候回调,因此我们的三角形在此处绘制.此方法会被循环回调 @Override protected void dispatchDraw(Canvas canvas) { canvas.save(); //移动画布,我们的三角形要随ViewPager的滑动而滑动, /** * 这里的思路: * 我们绘制三角形的坐标不变,因此我们的画布要移动。怎么移动?移动多少? * 很明显,三角形的移动和ViewPager的移动有关联。 * 而且这个移动值是一个动态变化的值。而通过addOnPageChangeListener()监听中的特定回调方法即可完成这个效果。offsetLenght这个变量就是接受ViewPager监听回调的。(初始为0) * / canvas.translate(initLenght+offsetLenght,getHeight()); canvas.drawPath(path,paint); canvas.restore(); super.dispatchDraw(canvas); }
OK,这样一个静态三角形就画好。上文中代码提到,想要三角形动,那么就要监听ViewPager的滑动,那么接下来让我们看一看ViewPager的监听。
//代码比较简单,而且注释已包含其中。 public void setViewPager(ViewPager viewPager){ this.viewPager=viewPager; viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() { @Override public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { /** * position:ViewPager中Fragment的位置。本例则为:0,1,2 * positionOffset:简单打印一下就会发现这个值。随着滑动的完成,数值的变化时0-1。 */ //我们在使用Canvas绘制三角形所用到的offsetLenght就是在这个方法中别动态赋值 setOffsetLength(position,positionOffset); setSelectTab(position); } @Override public void onPageSelected(int position) { //当我们选择特定的ViewPager内部对象时返回这个对象的位置。 } @Override public void onPageScrollStateChanged(int state) { /** * 基本上的滑动回调的状态都是这三种: * SCROLL_STATE_IDLE 当前未滚动(滚动停止后调用) * SCROLL_STATE_DRAGGING 当前正在被外部输入(如用户触摸输入)拖动(正在滑动,且手指不离开屏幕) * SCROLL_STATE_SETTLING 目前正在动画到最终位置,而不在外部控制之下(惯性滑动) */ } }); //设置默认Viewpager显示的页面 viewPager.setCurrentItem(0); setSelectTab(0); }
public void setOffsetLength(int position,float offsetLenght){ this.offsetLenght = (int) ((getWidth() / 3) * (offsetLenght + position)); if (position>=1){ //导航条的移动 //此方法用于View(也就是Tab)的滑动。 scrollTo((int) ((position-1)*getWidth()/3+getWidth()/3*offsetLenght),0); } //此方法作用在UI线程使自身重新绘制。重新调用draw()。 invalidate(); }
这样之后,我们三角形就可以跟随ViewPager的移动而移动。当然我们的Tab也可以随着滑动。核心就是上边那个方法中的scrollTo()。
到这我们基本上已经完成了大部分,接下啦就是Tab被选中的高亮效果和点击效果。
//这里真心没有啥好解释的- -! private void setSelectTab(int position){ resetTabColor(); if (getChildAt(position) instanceof TextView){ ((TextView) getChildAt(position)).setTextColor(Color.BLUE); ((TextView) //这种方式可以直接设置18sp getChildAt(position)).setTextSize(TypedValue.COMPLEX_UNIT_SP,18); } } private void resetTabColor() { for (int i = 0; i < getChildCount(); i++) { if (getChildAt(i) instanceof TextView){ ((TextView) getChildAt(i)).setTextColor(Color.WHITE); ((TextView) getChildAt(i)).setTextSize(TypedValue.COMPLEX_UNIT_SP,18); } } } //此方法应该在Tab被初始化的时候调用。 private void setTabClickEvent(){ resetTabColor(); for (int i=0;i<getChildCount();i++){ final int a=i; final View view=getChildAt(i); if (view instanceof TextView){ view.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { viewPager.setCurrentItem(a); } }); } } }
Ok,关于基本的自定义ViewPager指示器的部分就到此结束了。那么接下来便是:
二:开源项目分析
先看一波效果
此项目来自于:
https://github.com/hackware1993/MagicIndicator
篇幅过长,所以开源项目分析请移步:
http://blog.youkuaiyun.com/wjzj000/article/details/53664920
尾声
先记录到此,以后遇到实用的就继续加上。
最后希望各位看官可以star我的GitHub,三叩九拜,满地打滚求star:
https://github.com/zhiaixinyang/PersonalCollect
https://github.com/zhiaixinyang/MyFirstApp