------------------本博客如未明正声明转载,皆为原创,转载请注明出处!------------------

项目中需要一个日期选择控件,该日期选择控件是垂直滚动,停止滚动时需要校正日期数字位置,自动选择离中心位置最近的数字。效果如下:

利用继承LinearLayout实现,模仿Android带数据的控件的一般做法,加入适配器接口,选择事件监听接口,另外简单实现了子View的缓存,对应这样简单应用的情况下,应该是可以的,本人只用过TextView来做子控件,其他适配尚未测试,不知道效果如何。可能有其他的应用场景,分享给各位,可以修改或应用于你自己的项目。

下面贴代码,有点长O(∩_∩)O~:

/**
* 内容垂直滚动的一个控件,内容子项项垂直滚动后,将自动把当前离中心最近的项回滚至中心位置。
* 可根据{@link #getSelectedItem()} 获取当前选择的项,另外可添加选中事件监听器
* {@link #setOnItemSelectedListener(OnItemSelectedListener)}
*
* This component may contains several sub items, and the items is able to
* scroll up and down, by once released the scroll bar, the closest item will
* roll back to the center of component. use {@link #getSelectedItem()} to
* get the current selected item, and you can always use
* {@link #setOnItemSelectedListener(OnItemSelectedListener)} to do something
* after the item is selected.
*
* @date 2013/09/26
* @author Wison
*
*/
public class VerticalScrollAutoSelector extends LinearLayout { private ScrollView mContentScrollView;
private OnItemSelectedListener mOnItemSelectedListener; private AutoSelectorAdapter mAdapter;
private LinearLayout mItemsContainer;
private ViewGroup.LayoutParams mItemLayoutParams;
private TextView mStartBlankView;
private TextView mEndBlankView;
private List<View> mCachedSubViewList = new ArrayList<View>(); private int[] mItemViewsScrollYArr;
private Point mTouchedPoint = new Point();
private ScrollPointChecker mScrollPointChecker;
private int mSelectedPosition = -1; public VerticalScrollAutoSelector(Context context) {
this(context, null);
} @SuppressWarnings("deprecation")
public VerticalScrollAutoSelector(Context context, AttributeSet attrs) {
super(context, attrs); mContentScrollView = new ScrollView(getContext());
LinearLayout.LayoutParams linearLP = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.FILL_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
mContentScrollView.setLayoutParams(linearLP);
mContentScrollView.setVerticalScrollBarEnabled(false);
addView(mContentScrollView); mStartBlankView = new TextView(context);
mEndBlankView = new TextView(context);
mItemLayoutParams = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
mItemsContainer = new LinearLayout(context);
mItemsContainer.setOrientation(LinearLayout.VERTICAL);
mItemsContainer.setGravity(Gravity.CENTER);
mItemsContainer.setLayoutParams(mItemLayoutParams);
mContentScrollView.addView(mItemsContainer);
mContentScrollView.setOnTouchListener(new TimeScrollViewOnTouchListener());
} /**
* Register a callback to be invoked when an item in this VerticalScrollAutoSelector has been selected.
* @param listener The callback that will run
*/
public void setOnItemSelectedListener(OnItemSelectedListener listener) {
mOnItemSelectedListener = listener;
} /**
* Sets the data behind this VerticalScrollAutoSelector.
* @param adapter
*/
public void setAdapter(AutoSelectorAdapter adapter) {
mAdapter = adapter;
mSelectedPosition = -1;
mItemViewsScrollYArr = null;
mItemsContainer.removeAllViews();
if (mAdapter == null || mAdapter.getCount() <= 0) {
return;
} if (getHeight() == 0) {
// Waiting for component initialization finished
mContentScrollView.postDelayed(new Thread() {
@Override
public void run() {
attachAdapter();
}
}, 1);
} else {
attachAdapter();
}
} private void attachAdapter() {
if (getHeight() == 0) {
// try again!
setAdapter(mAdapter);
return;
} final int itemCount = mAdapter.getCount();
int itemGroup = mAdapter.getItemsCountPerGroup();
if (itemGroup < 3) {
itemGroup = 3;
} final float height = getHeight(); final int itemHeight = (int) (height / itemGroup);
int additionHeight = (int) (height - itemHeight * itemGroup); int itemPosition = 0;
final int totalHourItems = itemCount + 2; for (int i = 0; i < totalHourItems; i++) { if (i == 0 || i == totalHourItems - 1) {
TextView tv = (i == 0 ? mStartBlankView : mEndBlankView);
mItemLayoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT;
mItemLayoutParams.height = (--additionHeight >= 0) ? itemHeight + 1 : itemHeight;
tv.setLayoutParams(mItemLayoutParams);
mItemsContainer.addView(tv);
} else {
View convertView = null;
boolean isCached = true;
if (itemPosition < mCachedSubViewList.size()) {
convertView = mCachedSubViewList.get(itemPosition);
} else {
isCached = false;
} View view = mAdapter.getView(itemPosition, convertView, this);
view.setId(mAdapter.getItemId(itemPosition));
mItemLayoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT;
mItemLayoutParams.height = (--additionHeight >= 0) ? itemHeight + 1 : itemHeight;
view.setLayoutParams(mItemLayoutParams);
mItemsContainer.addView(view); if (!isCached) {
mCachedSubViewList.add(view);
} else {
if (view != convertView) {
mCachedSubViewList.remove(itemPosition);
mCachedSubViewList.add(itemPosition, view);
}
}
itemPosition++;
}
}
} /**
* Returns the adapter currently in use in this VerticalScrollAutoSelector.
* @return
*/
public AutoSelectorAdapter getAdapter() {
return mAdapter;
} /**
* Get the selected item.
* @return
*/
public Object getSelectedItem() {
if (mAdapter == null || mSelectedPosition < 0) return null;
return mAdapter.getItem(mSelectedPosition);
} private class TimeScrollViewOnTouchListener implements OnTouchListener { @Override
public boolean onTouch(View v, MotionEvent event) {
if (mAdapter == null || mAdapter.getCount() < 1) {
return false;
} switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
if (mItemViewsScrollYArr == null) {
int maxY = getMaxScrollY();
int[] location = new int[2];
mContentScrollView.getLocationOnScreen(location); final int itemsCount = getAdapter().getCount();
mItemViewsScrollYArr = new int[itemsCount];
mItemViewsScrollYArr[0] = 0;
mItemViewsScrollYArr[itemsCount - 1] = maxY; for (int i = 0; i < itemsCount - 2; i++) {
mItemViewsScrollYArr[i + 1] = (int) (1f * (i + 1) * maxY/ (itemsCount - 1));
}
} mTouchedPoint.x = mContentScrollView.getScrollX();
mTouchedPoint.y = mContentScrollView.getScrollY();
break;
case MotionEvent.ACTION_MOVE:
break;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
if (mScrollPointChecker == null) {
mScrollPointChecker = new ScrollPointChecker();
mScrollPointChecker.execute(mTouchedPoint);
} else {
mScrollPointChecker.cancel(true);
mScrollPointChecker = new ScrollPointChecker();
mScrollPointChecker.execute(mTouchedPoint);
}
break;
default:
break;
}
return false;
}
} /**
* 获得ScrollView最大垂直滚动距离
* @param scrollView
* @return
*/
private int getMaxScrollY() {
int tmpY = mContentScrollView.getScrollY(); mContentScrollView.scrollTo(getScrollX(), 5000);
int maxY = mContentScrollView.getScrollY(); mContentScrollView.scrollTo(mContentScrollView.getScrollX(), tmpY);
return maxY;
} private class ScrollPointChecker extends AsyncTask<Point, Integer, Integer> { private int oldScrollY = -1; @Override
protected Integer doInBackground(Point... params) {
if (params == null || params.length < 1) {
return -1;
}
Point originalPoint = params[0];
int scrollView_y = mContentScrollView.getScrollY();
if (scrollView_y == originalPoint.y) {
return -1;
} int currentPosition = -1; while (true) {
scrollView_y = mContentScrollView.getScrollY(); if (oldScrollY == scrollView_y) {
int tempPosition = -1; for (int i = 0; i < getAdapter().getCount(); i++) {
Rect visibleRect = new Rect();
boolean flag = mCachedSubViewList.get(i).getGlobalVisibleRect(visibleRect);
int[] location = new int[2];
mCachedSubViewList.get(i).getLocationOnScreen(location); if (flag && Math.abs(visibleRect.top - visibleRect.bottom) == mCachedSubViewList.get(i).getHeight()) {
if (tempPosition != -1) {
// compare with previous item, get the closer one.
if (Math.abs(mItemViewsScrollYArr[tempPosition] - scrollView_y) > Math.abs(mItemViewsScrollYArr[i] - scrollView_y)) {
tempPosition = i;
}
} else {
tempPosition = i;
}
}
} if (tempPosition != -1) {
currentPosition = tempPosition;
}
break;
} else {
oldScrollY = scrollView_y;
}
try {
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e) {
return -1;
}
}
return currentPosition;
} @Override
protected void onPostExecute(Integer result) {
if (result != -1) {
mSelectedPosition = result;
mContentScrollView.scrollTo(mContentScrollView.getScrollX(), mItemViewsScrollYArr[mSelectedPosition]);
if (mOnItemSelectedListener != null) {
mOnItemSelectedListener.onItemSelected(VerticalScrollAutoSelector.this,
mCachedSubViewList.get(mSelectedPosition), mSelectedPosition, mAdapter.getItemId(mSelectedPosition));
}
} else {
if (mOnItemSelectedListener != null) {
mOnItemSelectedListener.onNothingSelected(VerticalScrollAutoSelector.this);
}
}
}
} /**
* Interface definition for a callback to be invoked when
* an item in this view has been selected.
*/
public interface OnItemSelectedListener { void onItemSelected(VerticalScrollAutoSelector parent, View view, int position, int id); void onNothingSelected(VerticalScrollAutoSelector parent);
} /**
* Adapter for VerticalScrollAutoSelector
* @author Wison
*/
public abstract static class AutoSelectorAdapter { public boolean isEmpty() {
return getCount() == 0;
} /**
* Get the count of visible items when the VerticalScrollAutoSelector is displayed on the screen.
* @return
*/
public abstract int getItemsCountPerGroup(); /**
* Get the count of items
* @return
*/
public abstract int getCount(); /**
* Get the data item associated with the specified position in the data set.
* @param position
* @return
*/
public abstract Object getItem(int position); /**
* Get the row id associated with the specified position in the list.
* @param position
* @return
*/
public abstract int getItemId(int position); /**
* Get a View that displays the data at the specified position in the data set.
* @param position
* @param convertView
* @param parent
* @return
*/
public abstract View getView(int position, View convertView, ViewGroup parent); }
}

Android自定义垂直滚动自动选择日期控件的更多相关文章

  1. Android 自定义View 三板斧之一——继承现有控件

    通常情况下,Android实现自定义控件无非三种方式. Ⅰ.继承现有控件,对其控件的功能进行拓展. Ⅱ.将现有控件进行组合,实现功能更加强大控件. Ⅲ.重写View实现全新的控件 本文重点讨论继承现有 ...

  2. 专用于ASP.Net Web应用程序的日期控件

     原文引入:http://blog.csdn.net/nileel/article/details/1566051 专用于ASP.Net Web应用程序的日期控件 分类: ASP.NET/C#2007 ...

  3. Flex自定义组件开发之日周月日期选择日历控件

    原文:Flex自定义组件开发之日周月日期选择日历控件         使用过DateField的我们都知道,DateField 控件是用于显示日期的文本字段,字段右侧带有日历图标.当用户在控件边框内的 ...

  4. WPF自定义控件与样式(5)-Calendar/DatePicker日期控件自定义样式及扩展

    一.前言 申明:WPF自定义控件与样式是一个系列文章,前后是有些关联的,但大多是按照由简到繁的顺序逐步发布的等,若有不明白的地方可以参考本系列前面的文章,文末附有部分文章链接. 本文主要内容: 日历控 ...

  5. 安卓自定义日期控件(仿QQ,IOS7)

    还记得上篇:高大上的安卓日期时间选择器,本篇是根据上篇修改而来,先看下qq中日期选择的效果: 鉴于目前还没有相似的开源日期控件,因此本人花费了一些时间修改了下之前的日期控件,效果如图: 虽说相似度不是 ...

  6. FineUIPro v5.2.0已发布(jQuery升级,自定义图标,日期控件)

    FineUIPro/MVC/Core/JS v5.2.0 已经于 2018-8-20 发布,官网示例已更新,如果大家在测试中发现任何问题,请回复本帖,谢谢了. 在线示例: FineUI Pro:htt ...

  7. 【转】WPF自定义控件与样式(5)-Calendar/DatePicker日期控件自定义样式及扩展

    一.前言 申明:WPF自定义控件与样式是一个系列文章,前后是有些关联的,但大多是按照由简到繁的顺序逐步发布的等. 本文主要内容: 日历控件Calendar自定义样式: 日期控件DatePicker自定 ...

  8. 转 EasyUi日期控件datebox设置,只显示年月,也只能选择年月

    1.引入Jquery和easyui,注低版本的Jquery和easy不能使用,这里使用的Jquery是1.8.2easyui是1.6.1.1.easyui下载地址:http://www.jeasyui ...

  9. WPF自定义选择年月控件详解

    本文实例为大家分享了WPF自定义选择年月控件的具体代码,供大家参考,具体内容如下 封装了一个选择年月的控件,XAML代码: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 ...

随机推荐

  1. php引用计数与变量引用

    每个php5.5变量都存储在一个叫做zval的变量容器中. 一个zval变量容器,除了包含变量的类型与值外,还包含两个字节的额外信息: 1.第一个是“is_ref”,是个bool型,用来标识这个变量是 ...

  2. add BOM to fix UTF-8 in Excel

    fputs($fp, $bom =( chr(0xEF) . chr(0xBB) . chr(0xBF) ));

  3. AOP的实现原理——动态代理

    IOC负责将对象动态的 注入到容器,从而达到一种需要谁就注入谁,什么时候需要就什么时候注入的效果,可谓是招之则来,挥之则去.想想都觉得爽,如果现实生活中也有这本事那就爽 歪歪了,至于有多爽,各位自己脑 ...

  4. java 集合专练

    handsomecui的blog地址为:http://www.cnblogs.com/handsomecui/ 本人网站为:handsomecui.top 引言:本次主要练习单列集合:Collecti ...

  5. kaggle之手写体识别

    kaggle地址 数据预览 首先载入数据集 import pandas as pd import numpy as np train = pd.read_csv('/Users/frank/Docum ...

  6. 网页http请求的整个过程

    这几天看一个讲解一个网页从我们输入地址到显示在我们面前的一个讲解,是我对http又有了一个完整的了解,现在做一下整个流程的记录,虽然不是很详细,但是整个过程是完整的.如果不对,请指正! 打开浏览器,地 ...

  7. c#中的表达式

    // 把变量和字面值(在使用运算符时,将它们统称为操作数)与运算符组合起来 // 就可以创建表达式,表达式是计算的基本构件 // 操作数可以是数值也可以是变量 + ; ; int num3 = num ...

  8. jquery的验证实例方法

    上一篇介绍了js的正则验证法,这一片就用jquery来实例运行一下其中的几个方法 Js部分 <script> $(function(){ var ok1=false; var ok2=fa ...

  9. 未能加载文件或程序集“System.Web.Helpers, Version=2.0.0.0(转)

    在本地终于用上了ASP.NET MVC4自带的认证功能,但放到生产服务器上就出问题了:打开注册页面没问题,但一点下注册按钮就报错了: 未能加载文件或程序集“System.Web.Helpers, Ve ...

  10. 初学者的jquery登录注册和弹窗设计

    初次学习前端,接触到jquery,写了一个简单的注册账号, 并判断输入内容是否符合命名规则的页面效果如下: 首先创建html,js文件 在做页面布局之前还要连接js文件,然后开始布局自己的页面效果 i ...