滚轮选择控件
Android自带的选择时间控件有点丑,往往产品和设计都比较嫌弃,希望做成ios一样的滚轮选择,下面是我在NumberPicker的基础上自定义的选择控件,效果如下:
原理
实现滚轮效果有github上mark比较多的WheelView,但是阅读源码发现数据是一次性填入的,选择时间的话,填入10年就是10*365=3650条数据,也就是new出三千多个TextView,想想都觉得恐怖,肯定是不行的,于是便想到用NumberPicker,动态填充数据,一次只设置5个数据,当选中变化时,重新设置数据填充,所以关键在于填充的数据的计算。
设置数据部分逻辑代码:
/** * 更新左侧控件 * 日期:选择年控件 * 时间:选择月份和日期控件 * * @param timeMillis */ private void updateLeftValue(long timeMillis) { SimpleDateFormat sdf; String str[] = new String[DATA_SIZE]; if (mCurrentType == TYPE_PICK_DATE) { sdf = new SimpleDateFormat("yyyy"); for (int i = 0; i < DATA_SIZE; i++) { Calendar cal = Calendar.getInstance(); cal.setTimeInMillis(timeMillis); cal.add(Calendar.YEAR, (i - DATA_SIZE / 2)); str[i] = sdf.format(cal.getTimeInMillis()); } } else { sdf = new SimpleDateFormat("MM-dd EEE"); for (int i = 0; i < DATA_SIZE; i++) { Calendar cal = Calendar.getInstance(); cal.setTimeInMillis(timeMillis); cal.add(Calendar.DAY_OF_MONTH, (i - DATA_SIZE / 2)); str[i] = sdf.format(cal.getTimeInMillis()); } } mNpLeft.setDisplayedValues(str); mNpLeft.setValue(DATA_SIZE / 2); mNpLeft.postInvalidate(); }
对滚轮的监听,并重新设置填充数据:
@Override public void onValueChange(NumberPicker picker, int oldVal, int newVal) { Calendar calendar = Calendar.getInstance(); calendar.setTimeInMillis(mTimeMillis); int year = calendar.get(Calendar.YEAR); int month = calendar.get(Calendar.MONTH); int day = calendar.get(Calendar.DAY_OF_MONTH); int hour = calendar.get(Calendar.HOUR_OF_DAY); int offset = newVal - oldVal; if (picker == mNpLeft) { if (mCurrentType == TYPE_PICK_DATE) { calendar.add(Calendar.YEAR, offset); } else { calendar.add(Calendar.DAY_OF_MONTH, offset); } updateLeftValue(calendar.getTimeInMillis()); mTimeMillis = calendar.getTimeInMillis(); } else if (picker == mNpMiddle) { if (mCurrentType == TYPE_PICK_DATE) { calendar.add(Calendar.MONTH, offset); if (calendar.get(Calendar.YEAR) != year) { calendar.set(Calendar.YEAR, year); } } else { calendar.add(Calendar.HOUR_OF_DAY, offset); if (calendar.get(Calendar.DAY_OF_MONTH) != day) { calendar.set(Calendar.DAY_OF_MONTH, day); } if (calendar.get(Calendar.MONTH) != month) { calendar.set(Calendar.MONTH, month); } if (calendar.get(Calendar.YEAR) != year) { calendar.set(Calendar.YEAR, year); } } updateMiddleValue(calendar.getTimeInMillis()); updateRightValue(calendar.getTimeInMillis()); mTimeMillis = calendar.getTimeInMillis(); } else if (picker == mNpRight) { if (mCurrentType == TYPE_PICK_DATE) { int days = getMaxDayOfMonth(year, month + 1); if(day == 1 && offset < 0){ calendar.set(Calendar.DAY_OF_MONTH,days); }else if(day == days && offset > 0){ calendar.set(Calendar.DAY_OF_MONTH,1); }else{ calendar.add(Calendar.DAY_OF_MONTH, offset); } if (calendar.get(Calendar.MONTH) != month) { calendar.set(Calendar.MONTH, month); } if (calendar.get(Calendar.YEAR) != year) { calendar.set(Calendar.YEAR, year); } Log.e(TAG,"time:::"+test.format(calendar.getTimeInMillis())); } else { calendar.add(Calendar.MINUTE, offset); if (calendar.get(Calendar.HOUR_OF_DAY) != hour) { calendar.set(Calendar.HOUR_OF_DAY, hour); } if (calendar.get(Calendar.DAY_OF_MONTH) != day) { calendar.set(Calendar.DAY_OF_MONTH, day); } if (calendar.get(Calendar.MONTH) != month) { calendar.set(Calendar.MONTH, month); } if (calendar.get(Calendar.YEAR) != year) { calendar.set(Calendar.YEAR, year); } } updateRightValue(calendar.getTimeInMillis()); mTimeMillis = calendar.getTimeInMillis(); } /** * 向外部发送当前选中时间 */ if (mOnSelectedChangeListener != null) { mOnSelectedChangeListener.onSelected(this,mTimeMillis); } Log.e(TAG, "selected time:" + test.format(mTimeMillis)); }
选择数值和字符串
同样的,使用NumberPicker进行封装,动态填充数值从而实现滚动变换的效果。
完整代码如下:
package com.example.moore.picktimeview.widget; import android.content.Context; import android.graphics.Color; import android.util.AttributeSet; import android.util.Log; import android.view.Gravity; import android.view.ViewGroup; import android.widget.LinearLayout; import android.widget.NumberPicker; import android.widget.TextView; /** * Created by Moore on 2016/10/21. */ public class PickValueView extends LinearLayout implements NumberPicker.OnValueChangeListener { private Context mContext; /** * 组件 标题、单位、滚轮 */ private TextView mTitleLeft, mTitleMiddle, mTitleRight; private TextView mUnitLeft, mUnitMiddle, mUnitRight; private MyNumberPicker mNpLeft, mNpMiddle, mNpRight; /** * 数据个数 1列 or 2列 or 3列 */ private int mViewCount = 1; /** * 一组数据长度 */ private final int DATA_SIZE = 3; /** * 需要设置的值与默认值 */ private Object[] mLeftValues; private Object[] mMiddleValues; private Object[] mRightValues; private Object mDefaultLeftValue; private Object mDefaultMiddleValue; private Object mDefaultRightValue; /** * 当前正在显示的值 */ private Object[] mShowingLeft = new Object[DATA_SIZE]; private Object[] mShowingMiddle = new Object[DATA_SIZE]; private Object[] mShowingRight = new Object[DATA_SIZE]; /** * 步长 */ private int mLeftStep = 5; private int mMiddleStep = 1; private int mRightStep = 1; /** * 回调接口对象 */ private onSelectedChangeListener mSelectedChangeListener; public PickValueView(Context context) { super(context); this.mContext = context; generateView(); } public PickValueView(Context context, AttributeSet attrs) { super(context, attrs); this.mContext = context; generateView(); } public PickValueView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); this.mContext = context; generateView(); } /** * 生成视图 */ private void generateView() { //标题 LinearLayout titleLayout = new LinearLayout(mContext); LayoutParams titleParams = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); titleParams.setMargins(0, 0, 0, dip2px(12)); titleLayout.setLayoutParams(new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); titleLayout.setOrientation(HORIZONTAL); mTitleLeft = new TextView(mContext); mTitleMiddle = new TextView(mContext); mTitleRight = new TextView(mContext); LayoutParams params = new LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1); TextView[] titles = new TextView[]{mTitleLeft, mTitleMiddle, mTitleRight}; for (int i = 0; i < titles.length; i++) { titles[i].setLayoutParams(params); titles[i].setGravity(Gravity.CENTER); titles[i].setTextColor(Color.parseColor("#3434EE")); } titleLayout.addView(mTitleLeft); titleLayout.addView(mTitleMiddle); titleLayout.addView(mTitleRight); //内容 LinearLayout contentLayout = new LinearLayout(mContext); contentLayout.setLayoutParams(new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); contentLayout.setOrientation(HORIZONTAL); contentLayout.setGravity(Gravity.CENTER); mNpLeft = new MyNumberPicker(mContext); mNpMiddle = new MyNumberPicker(mContext); mNpRight = new MyNumberPicker(mContext); mUnitLeft = new TextView(mContext); mUnitMiddle = new TextView(mContext); mUnitRight = new TextView(mContext); MyNumberPicker[] nps = new MyNumberPicker[]{mNpLeft, mNpMiddle, mNpRight}; for (int i = 0; i < nps.length; i++) { nps[i].setLayoutParams(params); nps[i].setDescendantFocusability(FOCUS_BLOCK_DESCENDANTS); nps[i].setOnValueChangedListener(this); } contentLayout.addView(mNpLeft); contentLayout.addView(mUnitLeft); contentLayout.addView(mNpMiddle); contentLayout.addView(mUnitMiddle); contentLayout.addView(mNpRight); contentLayout.addView(mUnitRight); this.setLayoutParams(new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); this.setOrientation(VERTICAL); this.addView(titleLayout); this.addView(contentLayout); } /** * 初始化数据和值 */ private void initViewAndPicker() { if (mViewCount == 1) { this.mNpMiddle.setVisibility(GONE); this.mNpRight.setVisibility(GONE); this.mUnitMiddle.setVisibility(GONE); this.mUnitRight.setVisibility(GONE); } else if (mViewCount == 2) { this.mNpRight.setVisibility(GONE); this.mUnitRight.setVisibility(GONE); } //初始化数组值 if (mLeftValues != null && mLeftValues.length != 0) { if (mLeftValues.length < DATA_SIZE) { for (int i = 0; i < mLeftValues.length; i++) { mShowingLeft[i] = mLeftValues[i]; } for (int i = mLeftValues.length; i < DATA_SIZE; i++) { mShowingLeft[i] = -9999; } } else { for (int i = 0; i < DATA_SIZE; i++) { mShowingLeft[i] = mLeftValues[i]; } } mNpLeft.setMinValue(0); mNpLeft.setMaxValue(DATA_SIZE - 1); if (mDefaultLeftValue != null) updateLeftView(mDefaultLeftValue); else updateLeftView(mShowingLeft[0]); } /** * 中间控件 */ if (mViewCount == 2 || mViewCount == 3) { if (mMiddleValues != null && mMiddleValues.length != 0) { if (mMiddleValues.length < DATA_SIZE) { for (int i = 0; i < mMiddleValues.length; i++) { mShowingMiddle[i] = mMiddleValues[i]; } for (int i = mMiddleValues.length; i < DATA_SIZE; i++) { mShowingMiddle[i] = -9999; } } else { for (int i = 0; i < DATA_SIZE; i++) { mShowingMiddle[i] = mMiddleValues[i]; } } mNpMiddle.setMinValue(0); mNpMiddle.setMaxValue(DATA_SIZE - 1); if (mDefaultMiddleValue != null) updateMiddleView(mDefaultMiddleValue); else updateMiddleView(mShowingMiddle[0]); } } /** * 右侧控件 */ if (mViewCount == 3) { if (mRightValues != null && mRightValues.length != 0) { if (mRightValues.length < DATA_SIZE) { for (int i = 0; i < mRightValues.length; i++) { mShowingRight[i] = mRightValues[i]; } for (int i = mRightValues.length; i < DATA_SIZE; i++) { mShowingRight[i] = -9999; } } else { for (int i = 0; i < DATA_SIZE; i++) { mShowingRight[i] = mRightValues[i]; } } mNpRight.setMinValue(0); mNpRight.setMaxValue(DATA_SIZE - 1); if (mDefaultRightValue != null) updateRightView(mDefaultRightValue); else updateRightView(mShowingRight[0]); } } } private void updateLeftView(Object value) { updateValue(value, 0); } private void updateMiddleView(Object value) { updateValue(value, 1); } private void updateRightView(Object value) { updateValue(value, 2); } /** * 更新滚轮视图 * * @param value * @param index */ private void updateValue(Object value, int index) { String showStr[] = new String[DATA_SIZE]; MyNumberPicker picker; Object[] showingValue; Object[] values; int step; if (index == 0) { picker = mNpLeft; showingValue = mShowingLeft; values = mLeftValues; step = mLeftStep; } else if (index == 1) { picker = mNpMiddle; showingValue = mShowingMiddle; values = mMiddleValues; step = mMiddleStep; } else { picker = mNpRight; showingValue = mShowingRight; values = mRightValues; step = mRightStep; } if (values instanceof Integer[]) { for (int i = 0; i < DATA_SIZE; i++) { showingValue[i] = (int) value - step * (DATA_SIZE / 2 - i); int offset = (int) values[values.length - 1] - (int) values[0] + step; if ((int) showingValue[i] < (int) values[0]) { showingValue[i] = (int) showingValue[i] + offset; } if ((int) showingValue[i] > (int) values[values.length - 1]) { showingValue[i] = (int) showingValue[i] - offset; } showStr[i] = "" + showingValue[i]; } } else { int strIndex = 0; for (int i = 0; i < values.length; i++) { if (values[i].equals(value)) { strIndex = i; break; } } for (int i = 0; i < DATA_SIZE; i++) { int temp = strIndex - (DATA_SIZE / 2 - i); if (temp < 0) { temp += values.length; } if (temp >= values.length) { temp -= values.length; } showingValue[i] = values[temp]; showStr[i] = (String) values[temp]; } } picker.setDisplayedValues(showStr); picker.setValue(DATA_SIZE / 2); picker.postInvalidate(); } @Override public void onValueChange(NumberPicker picker, int oldVal, int newVal) { if (picker == mNpLeft) { updateLeftView(mShowingLeft[newVal]); } else if (picker == mNpMiddle) { updateMiddleView(mShowingMiddle[newVal]); } else if (picker == mNpRight) { updateRightView(mShowingRight[newVal]); } if (mSelectedChangeListener != null) { mSelectedChangeListener.onSelected(this, mShowingLeft[DATA_SIZE / 2], mShowingMiddle[DATA_SIZE / 2], mShowingRight[DATA_SIZE / 2]); } } /** * 设置数据--单列数据 * * @param leftValues * @param mDefaultLeftValue */ public void setValueData(Object[] leftValues, Object mDefaultLeftValue) { this.mViewCount = 1; this.mLeftValues = leftValues; this.mDefaultLeftValue = mDefaultLeftValue; initViewAndPicker(); } /** * 设置数据--两列数据 * * @param leftValues * @param mDefaultLeftValue * @param middleValues * @param defaultMiddleValue */ public void setValueData(Object[] leftValues, Object mDefaultLeftValue, Object[] middleValues, Object defaultMiddleValue) { this.mViewCount = 2; this.mLeftValues = leftValues; this.mDefaultLeftValue = mDefaultLeftValue; this.mMiddleValues = middleValues; this.mDefaultMiddleValue = defaultMiddleValue; initViewAndPicker(); } /** * 设置数据--三列数据 * * @param leftValues * @param mDefaultLeftValue * @param middleValues * @param defaultMiddleValue * @param rightValues * @param defaultRightValue */ public void setValueData(Object[] leftValues, Object mDefaultLeftValue, Object[] middleValues, Object defaultMiddleValue, Object[] rightValues, Object defaultRightValue) { this.mViewCount = 3; this.mLeftValues = leftValues; this.mDefaultLeftValue = mDefaultLeftValue; this.mMiddleValues = middleValues; this.mDefaultMiddleValue = defaultMiddleValue; this.mRightValues = rightValues; this.mDefaultRightValue = defaultRightValue; initViewAndPicker(); } /** * 设置左边数据步长 * * @param step */ public void setLeftStep(int step) { this.mLeftStep = step; initViewAndPicker(); } /** * 设置中间数据步长 * * @param step */ public void setMiddleStep(int step) { this.mMiddleStep = step; initViewAndPicker(); } /** * 设置右边数据步长 * * @param step */ public void setRightStep(int step) { this.mRightStep = step; initViewAndPicker(); } /** * 设置标题 * * @param left * @param middle * @param right */ public void setTitle(String left, String middle, String right) { if (left != null) { mTitleLeft.setVisibility(VISIBLE); mTitleLeft.setText(left); } else { mTitleLeft.setVisibility(GONE); } if (middle != null) { mTitleMiddle.setVisibility(VISIBLE); mTitleMiddle.setText(middle); } else { mTitleMiddle.setVisibility(GONE); } if (right != null) { mTitleRight.setVisibility(VISIBLE); mTitleRight.setText(right); } else { mTitleRight.setVisibility(GONE); } this.postInvalidate(); } public void setUnitLeft(String unitLeft) { setUnit(unitLeft, 0); } public void setmUnitMiddle(String unitMiddle) { setUnit(unitMiddle, 1); } public void setUnitRight(String unitRight) { setUnit(unitRight, 2); } private void setUnit(String unit, int index) { TextView tvUnit; if (index == 0) { tvUnit = mUnitLeft; } else if (index == 1) { tvUnit = mUnitMiddle; } else { tvUnit = mUnitRight; } if (unit != null) { tvUnit.setText(unit); } else { tvUnit.setText(" "); } initViewAndPicker(); } /** * 设置回调 * * @param listener */ public void setOnSelectedChangeListener(onSelectedChangeListener listener) { this.mSelectedChangeListener = listener; } /** * dp转px * * @param dp * @return */ private int dip2px(int dp) { float scale = mContext.getResources().getDisplayMetrics().density; return (int) (scale * dp + 0.5f); } /** * 回调接口 */ public interface onSelectedChangeListener { void onSelected(PickValueView view, Object leftValue, Object middleValue, Object rightValue); } }
关于NumberPicker
默认的NumberPicker往往字体颜色、分割线颜色等都是跟随系统,不能改变,考虑到可能比较丑或者有其他需求,所以自定义的NumberPicker,通过反射的方式更改里面的一些属性,代码如下:
package com.example.moore.picktimeview.widget; import android.content.Context; import android.graphics.Color; import android.graphics.drawable.ColorDrawable; import android.util.AttributeSet; import android.util.Log; import android.view.View; import android.view.ViewGroup; import android.widget.EditText; import android.widget.ImageButton; import android.widget.NumberPicker; import java.lang.reflect.Field; /** * Created by Moore on 2016/10/20. */ public class MyNumberPicker extends NumberPicker { private static int mTextSize = 16; private static int mTextColor = 0x000000; private static int mDividerColor = 0xFFFF00; public MyNumberPicker(Context context) { super(context); setNumberPickerDividerColor(); } public MyNumberPicker(Context context, AttributeSet attrs) { super(context, attrs); setNumberPickerDividerColor(); } public MyNumberPicker(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); setNumberPickerDividerColor(); } @Override public void addView(View child) { super.addView(child); updateView(child); } @Override public void addView(View child, int index, ViewGroup.LayoutParams params) { super.addView(child, index, params); updateView(child); } @Override public void addView(View child, ViewGroup.LayoutParams params) { super.addView(child, params); updateView(child); } private void updateView(View view) { if (view instanceof EditText) { // ((EditText) view).setTextSize(mTextSize); ((EditText) view).setTextSize(17); // ((EditText) view).setTextColor(mTextColor); ((EditText) view).setTextColor(Color.parseColor("#6495ED")); } } private void setNumberPickerDividerColor() { Field[] pickerFields = NumberPicker.class.getDeclaredFields(); /** * 设置分割线颜色 */ for (Field pf : pickerFields) { if (pf.getName().equals("mSelectionDivider")) { pf.setAccessible(true); try { // pf.set(this, new ColorDrawable(mDividerColor)); pf.set(this, new ColorDrawable(Color.parseColor("#C4C4C4"))); } catch (IllegalAccessException e) { e.printStackTrace(); } break; } } /** * 设置分割线高度 */ for (Field pf : pickerFields) { if (pf.getName().equals("mSelectionDividerHeight")) { pf.setAccessible(true); try { pf.set(this, 2); } catch (IllegalAccessException e) { e.printStackTrace(); } break; } } for (Field pf : pickerFields) { if (pf.getName().equals("mSelectorElementHeight")) { pf.setAccessible(true); try { pf.set(this, 2); } catch (IllegalAccessException e) { e.printStackTrace(); } break; } } } public void setDividerColor(int color) { this.mDividerColor = color; // this.postInvalidate(); } public void setTextColor(int color) { this.mTextColor = color; // this.postInvalidate(); } public void setTextSize(int textSize) { this.mTextSize = textSize; // this.postInvalidate(); } }
完整Demo可前往github查看与下载,地址:https://github.com/lizebinbin/PickTimeView.git 谢谢!
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持小牛知识库。
本文向大家介绍轻松实现可扩展自定义的Android滚轮时间选择控件,包括了轻松实现可扩展自定义的Android滚轮时间选择控件的使用技巧和注意事项,需要的朋友参考一下 项目需求中有个功能模块需要用到时间选择控件,但是android系统自带的太丑了,只能自己优化下,结合WheelView实现滚轮选择日期,好像网上也挺多这种文章的。但是适用范围还是不同,希望这个能够对需求相同的朋友有一定帮助。控件标题
如何实现时间选择器(以及日期选择器)的这种布局?默认布局要么是一个(非常大的)时钟,要么是一个(不漂亮的)旋转器样式的时间选择器。我在几个应用程序中看到了这一点,但找不到如何实现这种外观和感觉。
本文向大家介绍使用Android造了个滚轮控件轮子示例,包括了使用Android造了个滚轮控件轮子示例的使用技巧和注意事项,需要的朋友参考一下 关于 Android 实现 iOS 上的滚轮选择效果的控件,到 github 上一搜一大堆,之所以还要造这个轮子,目的是为了更好的学习自定义控件,这个控件是几个月前写的了,经过一段时间的完善,现在开源,顺便写这一篇简单的介绍文章。 效果如下,录屏软件看起来
本文向大家介绍bootstrap daterangepicker双日历时间段选择控件详解,包括了bootstrap daterangepicker双日历时间段选择控件详解的使用技巧和注意事项,需要的朋友参考一下 双日历时间段选择插件 — daterangepicker是bootstrap框架后期的一个时间控件,可以设定多个时间段选项,也可以自定义时间段,由用户自己选择起始时间和终止时间,时间段的最
本文向大家介绍Android convinientbanner顶部广告轮播控件使用详解,包括了Android convinientbanner顶部广告轮播控件使用详解的使用技巧和注意事项,需要的朋友参考一下 本文实例为大家分享了convinientbanner顶部广告轮播控件的具体代码,供大家参考,具体内容如下 gradle中添加 compile 'com.bigkoo:convenientban
本文向大家介绍微信小程序时间选择插件使用详解,包括了微信小程序时间选择插件使用详解的使用技巧和注意事项,需要的朋友参考一下 微信小程序时间选择插件-弹出选择,供大家参考,具体内容如下 wxml js: css: 以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持呐喊教程。