您的位置:首页 > 移动开发 > Android开发

安卓开发 简单实现自定义横向滚动选择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 ,有问题或者建议欢饮提出,大家共同进步,不喜勿喷,谢谢!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
相关文章推荐