1 引言
1.1 背景。
在以往的开发项目VR助手中有一个交互,就是利用波纹的指示器来实现Tab的切换。
2 交互方式
Paste_Image.png1)排行向全部过度时,有一个波纹走向:从初始点往高点移动。
2)全部向排行过度,由高点向低点移动,到排行这个按钮时时为最高峰。
从上图可以看到,当排行向全部的Tab划过时,需要有一个波纹的走向,以现有的Metrial Design提供的TabLayout是没法实现该效果的。
3 问题
1.TabLayout中的指示器是如何做到过渡的效果?
2.根据波纹的走向要使用什么数学模型?
4 解决思路
根据第一个问题,利用手机上的开发者模式中,打开显示Layout布局方式,从中看到,TabView是多个,而指示器应该是一个。为了验证整个问题,决定从源码上分析整个TabLayout的实现方式,来验证自己的判断,并将原生的部分源码COPY出来,改造一个新的TabLayout。
Paste_Image.png从上图可以看出,TabLayout分为几个模块:
1.SlidingTabStrip:TabView的容器类,用于承载TabView以及TabView滑动过程中,绘制的指示器。
2.TabView:一个Tab的View控件,支持自定义Tab,设置Tab的icon,text等,并且可以做特殊的特效控件。
3.Tab:TabView的参数类。
按照项目协作的方式理解就是:
1.RippleTabLayout就是一个领导,领导所要做的事有:
1)指派员工处理某件事(addTab,removeTab,selectTab,传递外部人员给与的参数)。
2)跟外部人员对接本项目(ViewPager,暴露监听回调。
2.SlidingTabStrip:程序员
1)努力实现领导下达的任务(onDraw):根据领导下发的外部对接参数,来绘制指示器。
2)由于SlidingTabStrip继承了LinearLayout,因此,也具备了add、remove、select的操作。
这时候,外部人员(ViewPager)传入参数,告知领导RippleTabLayout(positionOffset),程序员为了保证动态的效果,加入了动画(ValueAnimator)。当每接收到一次参数时,重新绘制一次指示器。
经验证发现,真正能实现波纹动效方面的是程序员(SlidingTabStrip)而非领导,那么我们要如何才能实现这种动态效果呢。当然还是让程序员来实现最好。
于是,我们在SlidingTabStrip中引入的高斯函数,为其绘制出动态效果。
Paste_Image.png高斯函数,广泛用于计算机图形学中,在起初进行开发前,数学模型采用的是其它线性模型,经过对交互的揣摩,后面选择了高斯滤波。在这里感谢谢炜斌、曾承航和已离职的陈鹏同学,大家针对高斯函数的方式进行了优化,并给予了算法。
5 程序改造
既然有了思路,就要着手解决这个交互。
1)提取原TbaLayout中的AnimationUtils、TabView、ViewUtils、ViewUtilsLopLipop进行部分代码改造。
2)重写SlidingTabStrip,并在SlidingTabStrip的onDraw方法中加入优化后的高斯滤波算法。核心代码如下:
//绘制高斯函数,根据线性大小计算值
private double getGussion(double lineX, double GussionHight, double GussionMu)
{
double result = 0;
result = -GussionHight * ( Math.pow(Math.E, -(lineX - GussionMu) * (lineX - GussionMu) / (2 * dpToPx(8) * dpToPx(8)))) + getHeight() - 3 ;
return result;
}
3)每一次的移动,手没放开,都在不断的onDraw
protected void onDraw(Canvas canvas)
{
super.onDraw(canvas);
Log.i("draw", "mGussionHeightOffset start= " );
Path path = new Path();
path.moveTo(0, getHeight());
for (int i = 0; i < getWidth(); i++){
double y1 = getGussion(i, dpToPx(15) * Math.abs(mGussionHeightOffset - 0.5), (mIndicatorRight + mIndicatorLeft) / 2);
double y2 = getGussion(i + 1, dpToPx(15) * Math.abs(mGussionHeightOffset - 0.5), (mIndicatorRight + mIndicatorLeft) / 2);
path.quadTo(i, (float) y1, i + 1 , (float)y2);
}
canvas.drawPath(path, mSelectedIndicatorPaint);
Log.i("draw", "mGussionHeightOffset end= " );
}
6 总结
代码能力编写再强,也要学会如何用数学模型思维来解决问题。
以下是该控件实现的相关源码:
https://github.com/RichsJeson/Android-UI/tree/master/AndroidUI/RippleLayout
网友评论