定制一个类似地址选择器的view
前言:
这几天也是闲来无事,看看有什么和Scroller相关的控件需要巩固下,原因很简单,前几天看到相关的控件:不错的一个卷尺view,于是乎自己也不能光看别人的demo啊,所以自己也就撸了一个带有滑动的地址选择器的view了。
view的来源gif图:
看到这的时候,我就大致有点思路了,所以自己的地址选择器view也是能登场了。
自己撸的view:
由于这个地址的数据量太大了,我就随便弄了几个城市的数据。后续可以继续添加其他的数据。
使用:
布局:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.library.multiselct.MultiSelectView
android:id="@+id/select_view"
android:layout_width="match_parent"
android:layout_height="200dp"
android:background="@android:color/white" />
</LinearLayout>
对MultiSelectView选中内容的监听
MultiSelectView multiSelectView = (MultiSelectView) conentView.findViewById(R.id.select_view);
multiSelectView.setOnAllSelect(new MultiSelectView.OnAllSelect() {
@Override
public void select(String text) {
//回调的处理
((MainActivity) context).setAddress(text);
}
});
数据源的处理:
multiSelectView.validateList(Constant.initData());
讲解:
在讲解之前还是来一个整个view的布局情况草图:
从这里不难发现外层是一个ViewGroup
,里面是三个我们需要滑动处理的View
了。
添加3个MultiSelectItem的view
private void initItem() {
for (int i = 0; i < 3; i++) {
MultiSelectItem multiSelectItem = new MultiSelectItem(getContext());
//滑动的索引位置监听
multiSelectItem.setScrollListener(this);
addView(multiSelectItem);
}
}
对3个MultiSelectItem的view测量
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = MeasureSpec.getSize(widthMeasureSpec);
//这里子view的宽度按照父view的宽度平分
int childWidth = (int) (width * 1.0f / getChildCount());
for (int i = 0; i < getChildCount(); i++) {
measureChild(getChildAt(i), MeasureSpec.makeMeasureSpec(childWidth, MeasureSpec.EXACTLY), heightMeasureSpec);
}
}
对3个MultiSelectItem的view进行layout
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
for (int i = 0; i < getChildCount(); i++) {
View item = getChildAt(i);
//子view的横坐标起点根据(总宽度*i)/getChildCount())
item.layout((int) (getWidth() * i * 1.0f / getChildCount()), 0, (int) (getWidth() * i * 1.0f / getChildCount()) + item.getMeasuredWidth(), item.getMeasuredHeight())
}
}
MultiSelectView
代码也太简单了点吧,没错,这就是Viewgroup
三步曲代码。
对于父Viewgroup
的三步曲代码已经搞定了,下面要进入到子View(MultiSelectItem
)的代码中去看看了,首先完成下静态的分行处理,分行处理其实就是画行数-1条横线了。
画横线:
private void drawLine(Canvas canvas) {
int lineCount = DEFRAULT_DISPLAY_COUNT - 1;
for (int i = 0; i < lineCount; i++) {
//每条横线的y轴起点是(总高度 * (i + 1) / DEFRAULT_DISPLAY_COUNT)
canvas.drawLine(0, getHeight() * 1.0f * (i + 1) / DEFRAULT_DISPLAY_COUNT, getWidth(), getHeight() * 1.0f * (i + 1) / DEFRAULT_DISPLAY_COUNT, linePaint);
}
}
绘制内容:
private void drawItem(Canvas canvas) {
for (int i = 0; i < selectBeanList.size(); i++) {
SelectBean selectBean = selectBeanList.get(i);
String name = selectBean.name;
if (offset + i * diffY >= height || (offset + i * diffY) + diffY <= 0) {
continue;
} else {
//这个是缩放和透明度的代码,先不用看
if (i == currentIndex) {
textPaint.setTextSize(currentTextSize);
textPaint.setAlpha((int) (255 * currentAlpha));
} else {
textPaint.setTextSize(otherTextSize);
textPaint.setAlpha((int) (255 * otherAlpha));
}
Paint.FontMetrics fontMetrics = textPaint.getFontMetrics();
float allHeight = fontMetrics.descent - fontMetrics.ascent;
canvas.drawText(name, width * 1.0f / 2, offset + i * diffY + diffY / 2 - allHeight / 2 - fontMetrics.ascent, textPaint);
}
}
}
上面的绘制代码中,有两个变量offset
、diffY
,offset是当前view滑动到的位置,也即是我们第一个item的起点坐标,diffY是每一行需要的高度,可以看下他们的初始化的值。
offset和diffY初始化:
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
height = h;
width = w;
//offset起点坐标也即是我们第二行的坐标
offset = (float) (h * 1.0 / DEFRAULT_DISPLAY_COUNT);
//每一个间隔的y轴距离是(总高度/DEFRAULT_DISPLAY_COUNT)
diffY = (float) (h * 1.0 / DEFRAULT_DISPLAY_COUNT);
//省略代码
}
知道了这两个变量后,咋们再来看下绘制内容的代码,首先在遍历数据源的时候,有越界的判断,分别是有四种情况是在绘制区域外的:
offset + i * diffY > height:item的上边缘在height之下
(offset + i * diffY) + diffY < 0:item的下边缘在0之上
(offset + i * diffY == height):item的上边缘在height位置
(offset + i * diffY) + diffY == 0:item的下边缘在0这个位置
这里给一个offset初始状态下(offset=h * 1.0 / DEFRAULT_DISPLAY_COUNT)的草图出来,这里只画一个MultiSelectItem的情况:
滑动处理:
@Override
public boolean onTouchEvent(MotionEvent event) {
if (velocityTracker == null) {
velocityTracker = VelocityTracker.obtain();
}
velocityTracker.addMovement(event);
float y = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mScroller.forceFinished(true);
lastY = y;
dy = 0;
break;
case MotionEvent.ACTION_MOVE:
dy = y - lastY;
//滑动过程中,改变值的过程
validateValue();
break;
case MotionEvent.ACTION_UP:
//抬起的时候对速度进行处理
calculateVelocity();
break;
}
lastY = y;
return true;
}
滑动过程中对offset的处理:
private void validateValue() {
offset += dy;
if (offset <= maxOffset) {
offset = maxOffset;
}
if (offset >= minOffset) {
offset = minOffset;
}
scrollTochangeChilds();
postInvalidate();
}
//滑动的位置到了需要改变childs数据的时候了
private void scrollTochangeChilds() {
if (Math.abs(offset) % diffY <= maxDeviation) {
if (offset > 0) {
//如果offset在view的起点下面,计算的时候需要-diffY
currentIndex = Math.round(Math.abs(offset - diffY) * 1.0f / diffY);
} else {
//如果offset在view的起点上面,计算的时候需要+diffY
currentIndex = Math.round((Math.abs(offset) + diffY) * 1.0f / diffY);
}
//当前被选中的放大
currentTextSize = maxTextSize;
//当前被选中的alpha值最大
currentAlpha = maxAlpha;
//接口回调,给MultiSelectView刷新数据
if (mScrollListener != null) {
mScrollListener.end(this, currentIndex);
}
}
}
上面代码就是onMove的操作处理,其中上面有offset临界值处理:
maxOffset:滑动的最大的位置
minOffset:滑动的最小的位置
这两个值是哪来的呢:
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
//省略代码
minOffset = diffY;
maxOffset = -diffY * (this.selectBeanList.size() - 2);
}
这里我画两张草图大家就知道这两个临界值是怎么回事了:
相信看图能知道是怎么回事了吧,临界值就是这么来的。
上面的move操作里面还进行了一个currentIndex的处理,当认为Math.abs(offset) % diffY <= maxDeviation的时候,则需要重新获取新的被选中的index了。
抬起过程中对offset的处理:
private void calculateVelocity() {
velocityTracker.computeCurrentVelocity(1000);
float yVelocity = velocityTracker.getYVelocity();
//大于这个值才会被认为是fling
if (Math.abs(yVelocity) > minFlingVelocity) {
//如果是当前位置在maxOffset处了,并且继续往上滑动则不处理或者 当前位置在minOffset处了,并且继续往下滑动则不处理
if ((offset == maxOffset && yVelocity < 0) || (offset == minOffset && yVelocity
return;
}
int startY = Math.round(offset);
//结束位置通过速度来判断了
endY = Math.round(yVelocity / 10) + startY;
//结束位置也是需要进行限制的
if (endY <= maxOffset) {
endY = maxOffset;
}
if (endY >= minOffset) {
endY = minOffset;
}
//和move的时候计算currentIndex是一样的
if (endY > 0) {
currentIndex = Math.round(Math.abs(endY - diffY) * 1.0f / diffY);
} else {
currentIndex = Math.round((Math.abs(endY) + diffY) * 1.0f / diffY);
}
//endY的位置是需要diffY成整数倍的,并且是与currentIndex成反比的
endY = diffY - currentIndex * diffY;
mScroller.startScroll(0, startY, 0, (int) (endY - startY));
invalidate();
} else {
//如果滑动速度不是很大,不需要fling的
releaseMoveTo();
}
}
//松手的时候,移动到最近的一个index上
private void releaseMoveTo() {
if (offset > 0) {
currentIndex = Math.round(Math.abs(offset - diffY) * 1.0f / diffY);
} else {
currentIndex = Math.round((Math.abs(offset) + diffY) * 1.0f / diffY);
}
int startY = Math.round(offset);
endY = diffY - currentIndex * diffY;
mScroller.startScroll(0, startY, 0, (int) (endY - startY));
invalidate();
}
@Override
public void computeScroll() {
super.computeScroll();
//返回true表示滑动还没有结束
if (mScroller.computeScrollOffset()) {
offset = mScroller.getCurrY();
scrollTochangeChilds();
postInvalidate();
}
}
对于MultiSelectItem整个代码基本就是这些了,可能还就是一些数据源的初始化和变量的一些初始化没说了,重点都已经介绍完了。
剩下还有MultiSelectView中被选中时的数据回调了,这里我就直接贴代码了:
@Override
public void end(MultiSelectItem multiSelectItem, int index) {
//如果是第1个MultiSelectItem中的某一个item被选中的话
if (multiSelectItem == getChildAt(0)) {
Log.d("MultiSelectView", "end:" + index);
this.selectBeanList2 = this.selectBeanList1.get(index).childs;
this.selectBeanList3 = this.selectBeanList2.get(0).childs;
((MultiSelectItem) getChildAt(1)).resetList(this.selectBeanList2);
((MultiSelectItem) getChildAt(2)).resetList(this.selectBeanList3);
if (onAllSelect != null) {
onAllSelect.select(this.selectBeanList1.get(index).name + " " + this.selectBeanList2.get(0).name + " " + this.selectBeanList3.get(0).name);
}
} else if (multiSelectItem == getChildAt(1)) {//如果是第2个MultiSelectItem中的某一个item被选中的话
this.selectBeanList3 = this.selectBeanList2.get(index).childs;
((MultiSelectItem) getChildAt(2)).resetList(this.selectBeanList3);
if (onAllSelect != null) {
onAllSelect.select(this.selectBeanList1.get(((MultiSelectItem) getChildAt(0)).getCurrentIndex()).name + " " + this.selectBeanList2.get(index).name + " " + this.selectBeanList3.get(0).name);
}
} else {
//如果是第3个MultiSelectItem中的某一个item被选中的话
if (onAllSelect != null) {
onAllSelect.select(this.selectBeanList1.get(((MultiSelectItem) getChildAt(0)).getCurrentIndex()).name + " " + this.selectBeanList2.get(((MultiSelectItem) getChildAt(1)).getCurrentIndex()).name + " " + this.selectBeanList3.get(index).name);
}
}
}
private OnAllSelect onAllSelect;
public void setOnAllSelect(OnAllSelect onAllSelect) {
this.onAllSelect = onAllSelect;
}
//选中内容回调
public interface OnAllSelect {
void select(String text);
}
总结:
MultiSelectView
中添加3个MultiSelectItem
MultiSelectView
中对3个MultiSelectItem
进行测量MultiSelectView
中对3个MultiSelectItem
进行layoutMultiSelectItem
首先把静态的行分割线画出来MultiSelectItem
中onTouch
的处理,边界、索引等MultiSelectView
中完成被选中的item的内容回调
项目文件目录截图:
项目结构
定制一个类似地址选择器的view
注:本文著作权归作者,由demo大师代发,拒绝转载,转载需要作者授权
定制一个类似地址选择器的view的更多相关文章
- 突破css选择器的局限,实现一个css地址选择器?
首先看一个效果,注意地址栏的变化 然后思考一下,用css如何实现? css选择器的局限 选择器是css中的一大特色,用于选择需要添加样式的元素. 选择器的种类有很多,比如 元素选择器 p {color ...
- 使用plupload做一个类似qq邮箱附件上传的效果
公司项目中使用的框架是springmvc+hibernate+spring,目前需要做一个类似qq邮箱附件上传的功能,暂时只是上传小类型的附件 处理过程和解决方案都需要添加附件,处理过程和解决方案都可 ...
- 制作一个类似苹果VFL的格式化语言来描述UIStackView
在项目中总是希望页面上各处的文字,颜色,字体大小甚至各个视图控件布局都能够在发版之后能够修改以弥补一些前期考虑不周,或者根据统计数据能够随时进行调整,当然是各个版本都能够统一变化.看到这样的要求后,第 ...
- 如何制作一个类似Tiny Wings的游戏(2) Cocos2d-x 2.1.4
在第二篇<如何制作一个类似Tiny Wings的游戏>基础上,增加添加主角,并且使用Box2D来模拟主角移动,原文<How To Create A Game Like Tiny Wi ...
- 使用 App Studio 快速定制一个你自己的专属应用
使用 App Studio 快速定制一个你自己的专属应用 如果已有做一个手机应用的想法,只需要一些简单的图片,视频,或者RSS源,就可以通过App Studio制作出你心仪的应用! App Studi ...
- 如何制作一个类似Tiny Wings的游戏 Cocos2d-x 2.1.4
在第一篇<如何使用CCRenderTexture创建动态纹理>基础上,增加创建动态山丘,原文<How To Create A Game Like Tiny Wings with Co ...
- 【Android Developers Training】 105. 显示一个位置地址
注:本文翻译自Google官方的Android Developers Training文档,译者技术一般,由于喜爱安卓而产生了翻译的念头,纯属个人兴趣爱好. 原文链接:http://developer ...
- 输入一个网站地址到网站展现的过程以及APR协议(鬼知道中间经历了什么)
以前只知道输入一个网站,然后看着返回琳琅满目的内容,其实中间经历的过程和步骤太多了.为了满足好奇心以及学习需要,特查阅了资料将其记录下来以备后续自己复习. 从我在地址栏输入www.zhihu.com ...
- 实现一个类似Chrome新功能提示的popoup
先让我们看一下Chrome的popup是什么样的: 这个“直接搜索网页”与“在打开的标签页之间切换”就是两个功能导航,还做了一个动画效果,会不停的上下晃. 我通过WindowManager的addVi ...
随机推荐
- Juce之旅-第一个例子(图形窗口)
以这么说现在的人越来越妖精了,本来软件吗,要的是简单稳定实用,但是看现在的趋势是越来越多人注重界面的美化和和效果.比如IM类软件,QQ,飞信还有土的掉渣的MSN等,前两天看了一下YahooUI,觉得不 ...
- 解决ASP.NET裁剪图片失真
//有的时候剪切图片时,出现图片失真的问题,是因为图片质量下降造成的 //按照指定的数据画出画板(位图) System.Drawing.Image imgPhoto = System.Drawing. ...
- HDU 2955 【01背包+小数概率】
Robberies Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others) Total Sub ...
- hiho一下第131周 后缀自动机二·重复旋律8(循环相似子串)
后缀自动机五·重复旋律8 时间限制:10000ms 单点时限:1000ms 内存限制:256MB 描述 小Hi平时的一大兴趣爱好就是演奏钢琴.我们知道一段音乐旋律可以被表示为一段数构成的数列. 小Hi ...
- 消息队列集群kafka安装配置
1. 下载wget http://mirror.rise.ph/apache/kafka/0.11.0.0/kafka_2.12-0.11.0.0.tgz2. 安装tar xf kafka_2.12- ...
- rsync + inotify 同步
1. 配置rysnc server:同步机,同步被同步机更新的文件,很多台vi /etc/rsyncd.conf uid=rootgid=rootuse chroot=nomax connection ...
- 使用参数化SQL
Java.C#等语言提供了参数化SQL机制,使用参数化SQL开发人员为在运行时才能确定的参数值设置占位符,在执行的时候再指定这些占位符所代表的值.示例代码如下: string user=txtUser ...
- 任务驱动,学习.NET开发系列第2篇------单词统计
一 高效学习编程的办法 1 任务驱动方式学习软件开发 大部分人学习软件开发技术是通过看书,看视频,听老师上课的方式.这些方式有一个共同点即按知识点进行讲解.比如拿c#编程为例,首先是讲解大量的基础概念 ...
- MTD
内存技术设备(英语:Memory Technology Device,缩写为 MTD),是Linux系统中设备文件系统的一个类别,主要用于快闪存储器的应用,是一种快闪存储器转换层(Flash Tran ...
- 【转】matlab 字符串处理函数
原文地址 matlab 字符串处理函数 % 字符串处理 a=' a';b='b b';c='cccc';m='' % 获取字符串长度 length(a) % 连接两个字符串,每个字符串最右 ...