安卓开发 简单实现自定义横向滚动选择View : HorizontalselectedView

来源:互联网 发布:国动网络董事长卢杰 编辑:程序博客网 时间:2024/06/01 20:25

一、需求:

今日产品经理让在产品里面加了个横向选择的功能,控件样子大致要求为:

这里写图片描述

网上找了好久没找到此控件,只能自己动手写了,很适合新手练习自定义View,并贡献给大家,效果如下:

这里写图片描述

其实很多滚轮控件也只是这个简单控件 组合一下就可以了 。

有任何问题可以加QQ群询问:661614986。

二、实现思路:

这里我偷懒了,没有把上、左、右三个箭头写到控件里面,写进去也简单,不过突然感觉在外面布局,写个方法出来也是蛮帅的。所以今天我们的主角就是中间的可以横向滑动的部分,乍一看就是个recycleview,不过这里我没有想过要用recycleview来实现,不是不可以,是用recycleview的话,各种判断、计算偏移量太多了,而且需求中要求只是文本,无需加载布局,所以为了节省时间就干脆自定义一个名为HorizontalselectedView的View,宽高无需自己计算,只需在onDraw()方法里面把每个String 画出来, 然后监听滑动事件或者点击左右箭头的时候,重走onDraw()方法就可以了,难点在于onDraw的时候,每个String的坐标如何获得

2.1、控件特征:

1、中间箭头下面的文字(被选中的文字)颜色和字体和其他的不一样
2、可以横向左右滑动,滑动过程当中,被选中的文字在变化
3、点击左右箭头的时候也可以 实现滚动,从而改变被选中的文字
4、可见区域内,显示的文字数是可以改变的
5、左右滑动的时候有回弹选择效果

根据以上特征,就能得到我们所需要的自定义属性,如下:

    <declare-styleable name="HorizontalselectedView">        <!--可见数目-->        <attr name="HorizontalselectedViewSeesize" format="integer"></attr>        <!--被选择文字的大小和颜色-->        <attr name="HorizontalselectedViewSelectedTextSize" format="float"></attr>        <attr name="HorizontalselectedViewSelectedTextColor" format="color|reference"></attr>        <!--未被被选择文字的大小和颜色-->        <attr name="HorizontalselectedViewTextSize" format="float"></attr>        <attr name="HorizontalselectedViewTextColor" format="color|reference"></attr>    </declare-styleable>

在构造方法里面初始化画笔和属性:

    public HorizontalselectedView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);        this.context = context;        setWillNotDraw(false);//使可以走到onDraw方法        initAttrs(attrs);//初始化属性        initPaint();//初始化画笔    }    /**     * 初始化属性     * @param attrs     */    private void initAttrs(AttributeSet attrs) {        TintTypedArray tta = TintTypedArray.obtainStyledAttributes(getContext(), attrs,                R.styleable.HorizontalselectedView);        //两种字体颜色和字体大小        seeSize = tta.getInteger(R.styleable.HorizontalselectedView_HorizontalselectedViewSeesize, 5);        selectedTextSize = tta.getFloat(R.styleable.HorizontalselectedView_HorizontalselectedViewSelectedTextSize, 50);        selectedColor = tta.getColor(R.styleable.HorizontalselectedView_HorizontalselectedViewSelectedTextColor, context.getResources().getColor(android.R.color.black));        textSize = tta.getFloat(R.styleable.HorizontalselectedView_HorizontalselectedViewTextSize, 40);        textColor = tta.getColor(R.styleable.HorizontalselectedView_HorizontalselectedViewTextColor, context.getResources().getColor(android.R.color.darker_gray));    }    /**     * 初始化画笔     */    private void initPaint() {        textPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);//普通文本画笔        textPaint.setTextSize(textSize);        textPaint.setColor(textColor);        selectedPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);//被选中文本画笔        selectedPaint.setColor(selectedColor);        selectedPaint.setTextSize(selectedTextSize);    }

2.2、重写onDraw()方法:

所有的文字里面,被选中的文字是特殊的,他的大小和颜色不一样,所有我们先把他给画出来,关键点在于要测量文本的宽高,代码如下:

      selectedPaint.getTextBounds(s, 0, s.length(), rect);            //从矩形区域中读出文本内容的宽高            int centerTextWidth = rect.width();            int centerTextHeight = rect.height();            canvas.drawText(strings.get(n), getWidth() / 2 - centerTextWidth / 2 ,                 getHeight() / 2 + centerTextHeight / 2, selectedPaint);//绘制被选中文字,注意点是y坐标,是以文字底部为中心

下面要做的是就是要遍历集合,把其他的文字给画出来,刚才也提到了,坐标是难点,我们看张图来理一理:

这里写图片描述

其实最巧妙的是得到图中所标注的一个单元的长度anInt,可见区域的长度除以可见个数就得到了 , 还有就是这个n,给到数据源的时候,我是让数据源集合的长度除以2的,这个可以理解的。然后遍历集合的时候 就可以根据下面得到x的坐标:x=width/2+anInt*(i-n)-textWidth/2,这里面的textWidth我们默认他一样长了,但实际情况中,可能会出现三位数,四位数,三个字,四个字,所以在代码中,我是获得被选中文字左右两面长度的平均值得到的。

    if (n > 0 && n < strings.size() - 1) {//获得中间被选中文字左右两边文本宽的平均值                    textPaint.getTextBounds(strings.get(n - 1), 0, strings.get(n - 1).length(), rect);                    int width1 = rect.width();                    textPaint.getTextBounds(strings.get(n + 1), 0, strings.get(n + 1).length(), rect);                    int width2 = rect.width();                    textWidth = (width1 + width2) / 2;                }

接下来就可以在onDraw()方法里面遍历了:

            for (int i = 0; i < strings.size(); i++) {//遍历strings,把每个地方都绘制出来,                if (n > 0 && n < strings.size() - 1) {                    textPaint.getTextBounds(strings.get(n - 1), 0, strings.get(n - 1).length(), rect);                    int width1 = rect.width();                    textPaint.getTextBounds(strings.get(n + 1), 0, strings.get(n + 1).length(), rect);                    int width2 = rect.width();                    textWidth = (width1 + width2) / 2;                }                if (i == 0) {//得到高,高度是一样的,所以无所谓                    textPaint.getTextBounds(strings.get(0), 0, strings.get(0).length(), rect);                    textHeight = rect.height();                }                if (i != n)                    canvas.drawText(strings.get(i), (i - n) * anInt + getWidth() / 2 - textWidth / 2 + anOffset, getHeight() / 2 + textHeight / 2, textPaint);//画出未被选中的每组文字            }

这样所得到的效果是把strings平铺在了控件上,其实已经有点样子了,下面就是进行滑动监听了。

迷糊了我们休息一下:

这里写图片描述

2.3、触屏监听事件:

触屏监听,自然而然就是要复写onTouchEvent 方法了 , 这里的触屏事件还是比较简单的,先贴上代码:

 @Override    public boolean onTouchEvent(MotionEvent event) {        switch (event.getAction()) {            case MotionEvent.ACTION_DOWN:                downX = event.getX();//获得点下去的x坐标                break;            case MotionEvent.ACTION_MOVE://复杂的是移动时的判断                float scrollX = event.getX();               if (scrollX > downX) {                    //向右滑动,当滑动距离大于每个单元的长度时,则改变被选中的文字。                    if (scrollX - downX >= anInt) {                        if (n > 0) {                             n = n - 1;                            downX = scrollX;                        }                    }                } else {                    //向左滑动,当滑动距离大于每个单元的长度时,则改变被选中的文字。                    if (downX - scrollX >= anInt) {                        if (n < strings.size() - 1) {                            n = n + 1;                            downX = scrollX;                        }                    }                }                invalidate();                break;            case MotionEvent.ACTION_UP:                //抬起手指时,重绘                invalidate();                break;            default:                break;        }        return super.onTouchEvent(event);    }

奇怪的是,在这里面打log,只有down事件触发,move和up事件触发不了,这就要加上一行代码:

setClickable(true);//使可点击

讲解:
down的时候记录下downX坐标值,然后move时根据scrollX 和downX的大小判断向左还是向右,

从而当scrollX -downX的绝对值刚好等于anInt的时候,n相应的加1 或者减一 , 再进行重绘,继而把此时的scrollX 赋值给doownX;

这个时候就实现了 左右滑动的时候,整个文本向右或者向左 一个单元一个单元的移动,这显然不是我们要的效果 , 我们要的是 , 滑动的过程当中 , 文本就跟着手势在动,所以这里面肯定需要个偏移量offSet;

   offset = scrollX - downX;

当移动一个单元长度的时候再把offset归零就可以了;所以最终每个文本的x坐标为:

   canvas.drawText(strings.get(i), (i - n) * anInt + getWidth() / 2 - textWidth / 2 + anOffset, getHeight() / 2 + textHeight / 2, textPaint);

那up的时候我们就要把offSet归零,然后经行重绘,就产生了回弹效果

这样就基本实现需求了,需要注意的是有几个地方需要加上n的大小判断的,不能让他小于0了,或者大于集合长度了,防止越界,源码已上传至github,点击可查看。

2.4、对外提供的一些方法:

 /**     * 设置个数据源     * @param strings 数据源String集合     */    public void setData(List<String> strings)  /**     * 改变中间可见文字的数目     * @param seeSizes 可见数     */    public void setSeeSize(int seeSizes)    /**     * 向左移动一个单元     */    public void setAnLeftOffset()    /**     * 向右移动一个单元     */    public void setAnRightOffset()    /**     * 获得被选中的文本     *     * @return 被选中的文本     */    public String getSelectedString()

三、经验总结:

本篇文章为适合初级程序员学习的典型自定义View,可能很多人看到效果都觉得一头雾水,不知用了什么高大上的东西实现的,其实一步步理下来,就是先展示,再让他动起来,最多就是难在计算坐标而已,木户的地方我们一定要善于画图去总结,发现里面的规律,最后祝各位工作顺利,幸福美满!

只贴出了部分代码,源码已上传至github,点击可查看,欢饮star,fork ,有问题或者建议欢饮提出,大家共同进步,不喜勿喷,谢谢!

6 0
原创粉丝点击