安卓开发 简单实现自定义横向滚动选择View : HorizontalselectedView
2017-05-08 15:47
471 查看
一、需求:
今日产品经理让在产品里面加了个横向选择的功能,控件样子大致要求为:网上找了好久没找到此控件,只能自己动手写了,很适合新手练习自定义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 ,有问题或者建议欢饮提出,大家共同进步,不喜勿喷,谢谢!
相关文章推荐
- 简单实现自定义横向滚动选择View
- Android 实现横向标题栏滚动效果(HorizontalScrollView + GridView + Viewpager + 自定义适配器)
- 安卓开发笔记——自定义HorizontalScrollView控件(实现QQ5.0侧滑效果)
- 开发----Swift自定义View实现 实现自动循环滚动
- 李洪强iOS开发之后使用XIB实现横向滚动的UIScrollView
- 【安卓-自定义布局】安卓App开发思路 一步一个脚印(十一)实现自定义左右滚动的导航栏目--仿美团
- 【安卓-自定义布局】安卓App开发思路 一步一个脚印(十)实现内嵌在app中的webview 腾讯开源X5 高效安全
- Android自定义View 简单实现多图片选择控件
- Android自定义ViewGroup实现可滚动的横向布局(2)
- Android自定义wheelview实现滚动日期选择器
- 安卓webview长按分享,长按选择,长按复制,仿好奇心日报长按分享自定义弹窗的实现
- 安卓的那些事儿-android之RecyclerView的使用,实现列表横向滚动
- Android 自定义 HorizontalScrollView 实现图片左右滚动按钮控制
- 【安卓-自定义布局】安卓App开发思路 一步一个脚印(七)实现ViewPager无限循环与自动播放
- android开发游记:viewpager关联tabs,自定义tabs实现翻页滚动效果
- 安卓开发之使用ViewDragHelper简单实现Activity左滑返回
- 【安卓-自定义布局】安卓App开发思路 一步一个脚印(六)实现ViewPager最基本的
- Android自定义View 简单实现多图片选择控件
- 安卓开发33:HorizontalScrollView 水平滚动 API
- Android开发自定义View的简单实现