欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

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

程序员文章站 2024-03-31 12:24:04
滚轮选择控件 android自带的选择时间控件有点丑,往往产品和设计都比较嫌弃,希望做成ios一样的滚轮选择,下面是我在numberpicker的基础上自定义的选择控件,...

滚轮选择控件

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

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。