Android好奇宝宝_番外篇_看脸的世界_07

来源:互联网 发布:linux电子书下载 编辑:程序博客网 时间:2024/06/09 17:27

废话少说,先上图:


(请看底部的4个点)


忘记是在那个APP上看到ViewPager底部的圆点指示器可以随着滚动而滚动的效果,便开始思考要怎么实现,最终发现效果实现很简单,拿来练手自定义View挺不错的。


写码之前:

写代码之前必须至少先有大概的思路,而且不要想到一点就开始写,必须对整体都大概心里有数再开始写。比如在实现这个效果时,刚开始我是想着重写线性布局,然后动态添加圆点,通过margin控制间隔。但是我发现这种办法在滚动时的处理逻辑编写起来比较复杂,既然只是几个圆点而已,直接继承View用画的方式画出来更简单。最终写出来的类加上一大把自动生成的代码,也才一百多行。

写代码并不难,想到正确的思路才难。


高清源码:


(1)初始化

public AnimDian(Context context, AttributeSet attrs) {super(context, attrs);TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.AnimDian);dianCount = array.getInteger(R.styleable.AnimDian_dian_count, 5);dianColor = array.getColor(R.styleable.AnimDian_dian_color, 0XFFFF0000);dianBgColor = array.getColor(R.styleable.AnimDian_dian_bg_color, 0X88FFFFFF);margin = array.getInteger(R.styleable.AnimDian_dian_margin, 20);dianSize = array.getInteger(R.styleable.AnimDian_dian_size, 20);array.recycle();init();}


一些自定义属性可以在xml中设置,关于自定义属性的教学网上一大把,我就不赘述了。

private void init() {// 初始化两支画笔dianPaint = new Paint();dianPaint.setAntiAlias(true);dianPaint.setColor(dianColor);bgPaint = new Paint();bgPaint.setAntiAlias(true);bgPaint.setColor(dianBgColor);}


初始化两支画笔,一支画显示选中的前景点,一支画背景点。


(2)测量大小

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {// 测量自身大小// 宽=点的宽度(直径)*点的个数+点之间的距离*(点的个数-1)int width = dianSize * dianCount + margin * (dianCount - 1);// 高=点的高度(直径)int height = dianSize;int wMeasureSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);int hMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);// 设置计算结果setMeasuredDimension(wMeasureSpec, hMeasureSpec);}


这个View的大小测量很简单,我也不浪费口水了。


(3)画

写一个自定义View是一个试验的过程,很少有一次性过把整个效果写完的,这里我们先考虑静态的,即没有滚动效果的该怎么写,然后再考虑怎么加上滚动效果。

先是静态的实现:

public void draw(Canvas canvas) {// 画背景点for (int i = 0; i < dianCount; i++) {drawBgDian(canvas, i);}//画选中状态的点if (selectPosition > -1) {drawDian(canvas);}}


静态的实现很简单,就是把几个背景的点画出来,再在选中位置的地方再画一个不同颜色的点遮盖住背景点。

注意:后画的东西会遮挡住先画的。


这里稍微复杂一点的就是位置的计算,不过只要认真思考现在已有的数据,以及是否有什么规律,画点草图,一般都不难解决。


画背景点:

private void drawBgDian(Canvas canvas, int i) {canvas.drawCircle((dianSize + margin) * i + dianSize / 2, dianSize / 2, dianSize / 2, bgPaint);}

圆心的Y坐标容易算,但X坐标需要画点草图想一下,让你的大脑动一下吧。


画选中状态的点跟背景点一样,只是只画一个,且用了不用颜色的画笔而已:

private void drawDian(Canvas canvas) {canvas.drawCircle((dianSize + margin) * selectPosition + dianSize / 2, dianSize / 2, dianSize / 2, dianPaint);}

就这样,静态效果已经完成了。现在开始思考怎么实现滚动效果:

首先,要让红点滚动,必须有滚动的数据,比如滚动的方向,滚动的距离。于是得先得到滚动的数据来源。

因为我们是用在ViewPager上的,所以很容易想到给ViewPager设置OnPageChangeListener,再把数据传给我们的AnimDian:

public void onPageScrolled(int arg0, float arg1, int arg2) {animDian.onPageScrolled(arg0, arg1, arg2);}

接下来就得搞清楚3个参数的含义,打印一下数值,得出结果是:



小结:

这里的返回参数并不受滑动方向的影响,一直以左边作为基准,在滑动时会有两个page显示在屏幕上:

arg0:左边page的index

arg1:左边page没有显示出来的部分的百分比,或者理解为右边page显示出来的部分的百分比

arg2:同arg1,不过是具体的像素值

我们这里只需要arg0和arg1,像素值我们用不着。

有了滚动的状态数据,就可以计算滚动点的位置了:

public void onPageScrolled(int arg0, float arg1, int arg2) {if (arg1 > 0 && arg1 < 1) {scrollState = STATE_SCROLLING;// 滚动时计算前景点距离左边的距离scrollDianCX = (dianSize + margin) * (float) (arg0 + arg1) + dianSize / 2;invalidate();}}

知道参数含义后怎么计算位置,还是需要我们再动下脑。


得到位置后就可以画出滚动点了:

private void drawScrollDian(Canvas canvas) {canvas.drawCircle(scrollDianCX, dianSize / 2, dianSize / 2, dianPaint);}

当然在滚动时,不应该画出选中位置的点,所以修改draw方法逻辑:

public void draw(Canvas canvas) {// 画背景点for (int i = 0; i < dianCount; i++) {drawBgDian(canvas, i);}// 如果不是在滚动状态,画选中位置的前景点if (selectPosition > -1 && scrollState != STATE_SCROLLING) {drawDian(canvas);}// 在滚动状态,画滚动点if (scrollState == STATE_SCROLLING) {drawScrollDian(canvas);}}

然后在滚动结束和选中位置发生改变时,同样要通知我们的AnimDian:

public void onPageScrollStateChanged(int arg0) {//arg0==0表示滚动状态为结束if (arg0 == 0) {animDian.onPageScrollEnd();}}public void onPageSelected(int arg0) {animDian.setSelectPosition(arg0);}

在滚动结束时要修改AnimDian的滚动状态:

public void onPageScrollEnd() {scrollState = STATE_READY;invalidate();}

同样,在选中位置改变时也要进行处理:

public void setSelectPosition(int selectPosition) {this.selectPosition = selectPosition;//外部可以设置监听选中位置的改变,一般用不着,直接监听ViewPager的就行了if (mListener != null)mListener.onSelectChange(selectPosition);//如果当前正在滚动,就没必要请求重绘了,等到滚动结束后会去请求重绘的if (scrollState == STATE_SCROLLING)return;invalidate();}

至此,这个自定义View就完成了。虽然有点简单,但是作为新手刚开始还是不要挑战太复杂的自定义View,先写点简单的找点成就感和自信,多思考和理解下原理。


准备回家过年了!!!


DEMO下载

1 0