软件编程
位置:首页>> 软件编程>> Android编程>> Android滚轮选择时间控件使用详解

Android滚轮选择时间控件使用详解

作者:lizebin_bin  发布时间:2022-06-07 21:38:47 

标签:Android,滚轮选择,时间控件

滚轮选择控件

Android自带的选择时间控件有点丑,往往产品和设计都比较嫌弃,希望做成ios一样的滚轮选择,下面是我在NumberPicker的基础上自定义的选择控件,效果如下:

Android滚轮选择时间控件使用详解

原理

  • 基于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进行封装,动态填充数值从而实现滚动变换的效果。

  • 考虑到通用性,传入的是Object类型的数组,在控件里进行判断。

  • 可以选择一列数值、两列数值、三列数值,字符串同理。每一列数值可以设置它的单位、标题等,默认是隐藏,需要自己设置。

  • 可以设置步长step

完整代码如下:


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 谢谢!

0
投稿

猜你喜欢

手机版 软件编程 asp之家 www.aspxhome.com