你的位置:首页 > 操作系统

[操作系统]Android自定义可循环的滚动选择器CycleWheelView 替代TimePicker/NumberPicker/WheelView


最近碰到个项目要使用到滚动选择器,原生的NumberPicker可定制性太差,不大符合UI要求。

 

网上开源的WheelView是用ScrollView写的,不能循环滚动,而且当数据量很大时要加载的Item太多,性能非常低。

 

然后,还是自己写一个比较靠谱,用的是ListView实现的。写完自己体验了一下,性能不错,再大的数据也不怕了。

 

感觉不错,重新封装了一下,提供了一些接口可以直接按照自己的需求定制,调用方法在MainActivity中。

 

不多说了,直接上代码:

 

CycleWheelView.java:

/** * Copyright (C) 2015 * * CycleWheelView.java * * Description: * * Author: Liao Longhui * * Ver 1.0, 2015-07-15, Liao Longhui, Create file */package com.example.wheelviewdemo;import android.content.Context;import android.graphics.Canvas;import android.graphics.Color;import android.graphics.ColorFilter;import android.graphics.Paint;import android.graphics.drawable.Drawable;import android.os.Handler;import android.util.AttributeSet;import android.view.LayoutInflater;import android.view.View;import android.view.ViewGroup;import android.widget.AbsListView;import android.widget.BaseAdapter;import android.widget.ListView;import android.widget.TextView;import java.util.ArrayList;import java.util.List;/** * 可循环滚动的选择器 * @author Liao Longhui * */public class CycleWheelView extends ListView {  public static final String TAG = CycleWheelView.class.getSimpleName();  private static final int COLOR_DIVIDER_DEFALUT = Color.parseColor("#747474");  private static final int HEIGHT_DIVIDER_DEFAULT = 2;  private static final int COLOR_SOLID_DEFAULT = Color.parseColor("#3e4043");  private static final int COLOR_SOLID_SELET_DEFAULT = Color.parseColor("#323335");  private static final int WHEEL_SIZE_DEFAULT = 3;  private Handler mHandler;  private CycleWheelViewAdapter mAdapter;  /**   * Labels   */  private List<String> mLabels;  /**   * Color Of Selected Label   */  private int mLabelSelectColor = Color.WHITE;  /**   * Color Of Unselected Label   */  private int mLabelColor = Color.GRAY;  /**   * Gradual Alph   */  private float mAlphaGradual = 0.7f;  /**   * Color Of Divider   */  private int dividerColor = COLOR_DIVIDER_DEFALUT;  /**   * Height Of Divider   */  private int dividerHeight = HEIGHT_DIVIDER_DEFAULT;  /**   * Color of Selected Solid   */  private int seletedSolidColor = COLOR_SOLID_SELET_DEFAULT;  /**   * Color of Unselected Solid   */  private int solidColor = COLOR_SOLID_DEFAULT;  /**   * Size Of Wheel , it should be odd number like 3 or greater   */  private int mWheelSize = WHEEL_SIZE_DEFAULT;  /**   * res Id of Wheel Item Layout   */  private int mItemLayoutId;  /**   * res Id of Label TextView   */  private int mItemLabelTvId;  /**   * Height of Wheel Item   */  private int mItemHeight;  private boolean cylceEnable;  private int mCurrentPositon;  private WheelItemSelectedListener mItemSelectedListener;  public CycleWheelView(Context context, AttributeSet attrs, int defStyle) {    super(context, attrs, defStyle);  }  public CycleWheelView(Context context, AttributeSet attrs) {    super(context, attrs);    init();  }  public CycleWheelView(Context context) {    super(context);  }  private void init() {    mHandler = new Handler();    mItemLayoutId = R.layout.item_cyclewheel;    mItemLabelTvId = R.id.tv_label_item_wheel;    mAdapter = new CycleWheelViewAdapter();    setVerticalScrollBarEnabled(false);    setScrollingCacheEnabled(false);    setCacheColorHint(Color.TRANSPARENT);    setFadingEdgeLength(0);    setOverScrollMode(OVER_SCROLL_NEVER);    setDividerHeight(0);    setAdapter(mAdapter);    setOnScrollListener(new OnScrollListener() {      @Override      public void onScrollStateChanged(AbsListView view, int scrollState) {        if (scrollState == SCROLL_STATE_IDLE) {          View itemView = getChildAt(0);          if (itemView != null) {            float deltaY = itemView.getY();            if (deltaY == 0) {              return;            }            if (Math.abs(deltaY) < mItemHeight / 2) {              smoothScrollBy(getDistance(deltaY), 50);            } else {              smoothScrollBy(getDistance(mItemHeight + deltaY), 50);            }          }        }      }      @Override      public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,          int totalItemCount) {        refreshItems();      }    });  }  private int getDistance(float scrollDistance) {    if (Math.abs(scrollDistance) <= 2) {      return (int) scrollDistance;    } else if (Math.abs(scrollDistance) < 12) {      return scrollDistance > 0 ? 2 : -2;    } else {      return (int) (scrollDistance / 6);    }  }  private void refreshItems() {    int offset = mWheelSize / 2;    int firstPosition = getFirstVisiblePosition();    int position = 0;    if (getChildAt(0) == null) {      return;    }    if (Math.abs(getChildAt(0).getY()) <= mItemHeight / 2) {      position = firstPosition + offset;    } else {      position = firstPosition + offset + 1;    }    if (position == mCurrentPositon) {      return;    }    mCurrentPositon = position;    if (mItemSelectedListener != null) {      mItemSelectedListener.onItemSelected(getSelection(), getSelectLabel());    }    resetItems(firstPosition, position, offset);  }  private void resetItems(int firstPosition, int position, int offset){    for (int i = position - offset - 1; i < position + offset + 1; i++) {      View itemView = getChildAt(i - firstPosition);      if (itemView == null) {        continue;      }      TextView labelTv = (TextView) itemView.findViewById(mItemLabelTvId);      if (position == i) {        labelTv.setTextColor(mLabelSelectColor);        itemView.setAlpha(1f);      } else {        labelTv.setTextColor(mLabelColor);        int delta = Math.abs(i - position);        double alpha = Math.pow(mAlphaGradual, delta);        itemView.setAlpha((float) alpha);      }    }  }    /**   * 设置滚轮的刻度列表   *   * @param labels   */  public void setLabels(List<String> labels) {    mLabels = labels;    mAdapter.setData(mLabels);    mAdapter.notifyDataSetChanged();    initView();  }  /**   * 设置滚轮滚动监听   *   * @param mItemSelectedListener   */  public void setOnWheelItemSelectedListener(WheelItemSelectedListener mItemSelectedListener) {    this.mItemSelectedListener = mItemSelectedListener;  }  /**   * 获取滚轮的刻度列表   *   * @return   */  public List<String> getLabels() {    return mLabels;  }  /**   * 设置滚轮是否为循环滚动   *   * @param enable true-循环 false-单程   */  public void setCycleEnable(boolean enable) {    if (cylceEnable != enable) {      cylceEnable = enable;      mAdapter.notifyDataSetChanged();      setSelection(getSelection());    }  }  /*   * 滚动到指定位置   */  @Override  public void setSelection(final int position) {    mHandler.post(new Runnable() {      @Override      public void run() {        CycleWheelView.super.setSelection(getPosition(position));      }    });  }  private int getPosition(int positon) {    if (mLabels == null || mLabels.size() == 0) {      return 0;    }    if (cylceEnable) {      int d = Integer.MAX_VALUE / 2 / mLabels.size();      return positon + d * mLabels.size();    }    return positon;  }  /**   * 获取当前滚轮位置   *   * @return   */  public int getSelection() {    if (mCurrentPositon == 0) {      mCurrentPositon = mWheelSize / 2;    }    return (mCurrentPositon - mWheelSize / 2) % mLabels.size();  }  /**   * 获取当前滚轮位置的刻度   *   * @return   */  public String getSelectLabel() {    int position = getSelection();    position = position < 0 ? 0 : position;    try {      return mLabels.get(position);    } catch (Exception e) {      return "";    }  }  /**   * 如果需要自定义滚轮每个Item,调用此方法设置自定义Item布局,自定义布局中需要一个TextView来显示滚轮刻度   *   * @param itemResId 布局文件Id   * @param labelTvId 刻度TextView的资源Id   */  public void setWheelItemLayout(int itemResId, int labelTvId) {    mItemLayoutId = itemResId;    mItemLabelTvId = labelTvId;    mAdapter = new CycleWheelViewAdapter();    mAdapter.setData(mLabels);    setAdapter(mAdapter);    initView();  }  /**   * 设置未选中刻度文字颜色   *   * @param labelColor   */  public void setLabelColor(int labelColor) {    this.mLabelColor = labelColor;    resetItems(getFirstVisiblePosition(), mCurrentPositon, mWheelSize/2);  }  /**   * 设置选中刻度文字颜色   *   * @param labelSelectColor   */  public void setLabelSelectColor(int labelSelectColor) {    this.mLabelSelectColor = labelSelectColor;    resetItems(getFirstVisiblePosition(), mCurrentPositon, mWheelSize/2);  }  /**   * 设置滚轮刻度透明渐变值   *   * @param alphaGradual   */  public void setAlphaGradual(float alphaGradual) {    this.mAlphaGradual = alphaGradual;    resetItems(getFirstVisiblePosition(), mCurrentPositon, mWheelSize/2);  }  /**   * 设置滚轮可显示的刻度数量,必须为奇数,且大于等于3   *   * @param wheelSize   * @throws CycleWheelViewException 滚轮数量错误   */  public void setWheelSize(int wheelSize) throws CycleWheelViewException {    if (wheelSize < 3 || wheelSize % 2 != 1) {      throw new CycleWheelViewException("Wheel Size Error , Must Be 3,5,7,9...");    } else {      mWheelSize = wheelSize;      initView();    }  }    /**   * 设置块的颜色   * @param unselectedSolidColor 未选中的块的颜色   * @param selectedSolidColor 选中的块的颜色   */  public void setSolid(int unselectedSolidColor, int selectedSolidColor){    this.solidColor = unselectedSolidColor;    this.seletedSolidColor = selectedSolidColor;    initView();  }    /**   * 设置分割线样式   * @param dividerColor 分割线颜色   * @param dividerHeight 分割线高度(px)   */  public void setDivider(int dividerColor, int dividerHeight){    this.dividerColor = dividerColor;    this.dividerHeight = dividerHeight;  }  @SuppressWarnings("deprecation")  private void initView() {    mItemHeight = measureHeight();    ViewGroup.LayoutParams lp = getLayoutParams();    lp.height = mItemHeight * mWheelSize;    mAdapter.setData(mLabels);    mAdapter.notifyDataSetChanged();    Drawable backgroud = new Drawable() {      @Override      public void draw(Canvas canvas) {        int viewWidth = getWidth();        Paint dividerPaint = new Paint();        dividerPaint.setColor(dividerColor);        dividerPaint.setStrokeWidth(dividerHeight);        Paint seletedSolidPaint = new Paint();        seletedSolidPaint.setColor(seletedSolidColor);        Paint solidPaint = new Paint();        solidPaint.setColor(solidColor);        canvas.drawRect(0, 0, viewWidth, mItemHeight * (mWheelSize / 2), solidPaint);        canvas.drawRect(0, mItemHeight * (mWheelSize / 2 + 1), viewWidth, mItemHeight            * (mWheelSize), solidPaint);        canvas.drawRect(0, mItemHeight * (mWheelSize / 2), viewWidth, mItemHeight            * (mWheelSize / 2 + 1), seletedSolidPaint);        canvas.drawLine(0, mItemHeight * (mWheelSize / 2), viewWidth, mItemHeight            * (mWheelSize / 2), dividerPaint);        canvas.drawLine(0, mItemHeight * (mWheelSize / 2 + 1), viewWidth, mItemHeight            * (mWheelSize / 2 + 1), dividerPaint);      }      @Override      public void setAlpha(int alpha) {      }      @Override      public void setColorFilter(ColorFilter cf) {      }      @Override      public int getOpacity() {        return 0;      }    };    setBackgroundDrawable(backgroud);  }  private int measureHeight() {    View itemView = LayoutInflater.from(getContext()).inflate(mItemLayoutId, null);    itemView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,        ViewGroup.LayoutParams.WRAP_CONTENT));    int w = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);    int h = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);    itemView.measure(w, h);    int height = itemView.getMeasuredHeight();    // int width = view.getMeasuredWidth();    return height;  }  public interface WheelItemSelectedListener {    public void onItemSelected(int position, String label);  }  public class CycleWheelViewException extends Exception {    private static final long serialVersionUID = 1L;    public CycleWheelViewException(String detailMessage) {      super(detailMessage);    }  }  public class CycleWheelViewAdapter extends BaseAdapter {    private List<String> mData = new ArrayList<String>();    public void setData(List<String> mWheelLabels) {      mData.clear();      mData.addAll(mWheelLabels);    }    @Override    public int getCount() {      if (cylceEnable) {        return Integer.MAX_VALUE;      }      return mData.size() + mWheelSize - 1;    }    @Override    public Object getItem(int position) {      return "";    }    @Override    public long getItemId(int position) {      return position;    }    @Override    public boolean isEnabled(int position) {      return false;    }    @Override    public View getView(int position, View convertView, ViewGroup parent) {      if (convertView == null) {        convertView = LayoutInflater.from(getContext()).inflate(mItemLayoutId, null);      }      TextView textView = (TextView) convertView.findViewById(mItemLabelTvId);      if (position < mWheelSize / 2          || (!cylceEnable && position >= mData.size() + mWheelSize / 2)) {        textView.setText("");        convertView.setVisibility(View.INVISIBLE);      } else {        textView.setText(mData.get((position - mWheelSize / 2) % mData.size()));        convertView.setVisibility(View.VISIBLE);      }      return convertView;    }  }}

 


MainActivity.java:

public class MainActivity extends Activity {  private CycleWheelView cycleWheelView0,cycleWheelView1, cycleWheelView2;  @Override  protected void onCreate(Bundle savedInstanceState) {    super.onCreate(savedInstanceState);    setContentView(R.layout.activity_main);    cycleWheelView0 = (CycleWheelView) findViewById(R.id.cycleWheelView);    List<String> labels = new ArrayList<>();    for (int i = 0; i < 12; i++) {      labels.add("" + i);    }    cycleWheelView0.setLabels(labels);    cycleWheelView0.setAlphaGradual(0.5f);    cycleWheelView0.setOnWheelItemSelectedListener(new WheelItemSelectedListener() {      @Override      public void onItemSelected(int position, String label) {        Log.d("test", label);      }    });        cycleWheelView1 = (CycleWheelView) findViewById(R.id.cycleWheelView1);    List<String> labels1 = new ArrayList<>();    for (int i = 0; i < 24; i++) {      labels1.add("" + i);    }    cycleWheelView1.setLabels(labels1);    try {      cycleWheelView1.setWheelSize(5);    } catch (CycleWheelViewException e) {      e.printStackTrace();    }    cycleWheelView1.setSelection(2);    cycleWheelView1.setWheelItemLayout(R.layout.item_cyclewheel_custom, R.id.tv_label_item_wheel_custom);    cycleWheelView1.setOnWheelItemSelectedListener(new WheelItemSelectedListener() {      @Override      public void onItemSelected(int position, String label) {        Log.d("test", label);      }    });    cycleWheelView2 = (CycleWheelView) findViewById(R.id.cycleWheelView2);    List<String> labels2 = new ArrayList<>();    for (int i = 0; i < 60; i++) {      labels2.add("" + i);    }    cycleWheelView2.setLabels(labels2);    try {      cycleWheelView2.setWheelSize(7);    } catch (CycleWheelViewException e) {      e.printStackTrace();    }    cycleWheelView2.setCycleEnable(true);    cycleWheelView2.setSelection(30);    cycleWheelView2.setAlphaGradual(0.6f);    cycleWheelView2.setDivider(Color.parseColor("#abcdef"), 2);    cycleWheelView2.setSolid(Color.WHITE,Color.WHITE);    cycleWheelView2.setLabelColor(Color.BLUE);    cycleWheelView2.setLabelSelectColor(Color.RED);    cycleWheelView2.setOnWheelItemSelectedListener(new WheelItemSelectedListener() {      @Override      public void onItemSelected(int position, String label) {        Log.d("test", label);      }    });  }}

Item_cyclewheel.

<??><RelativeLayout ="http://schemas.android.com/apk/res/android"  android:layout_width="match_parent"  android:layout_height="wrap_content"  android:padding="16dp"  android:background="@android:color/transparent" >  <TextView    android:id="@+id/tv_label_item_wheel"    android:layout_width="wrap_content"    android:layout_height="wrap_content"    android:textSize="20sp"    android:singleLine="true"    android:layout_centerHorizontal="true"    android:layout_centerVertical="true" /></RelativeLayout>

Item_cyclewheel_custom.

<??><RelativeLayout ="http://schemas.android.com/apk/res/android"  android:layout_width="match_parent"  android:layout_height="wrap_content"  android:padding="16dp"  android:background="@android:color/transparent" >  <TextView    android:id="@+id/tv_label_item_wheel_custom"    android:layout_width="wrap_content"    android:layout_height="wrap_content"    android:singleLine="true"    android:layout_alignParentLeft="true"    android:layout_centerVertical="true" />  <ImageView    android:layout_width="25dp"    android:layout_height="25dp"    android:layout_centerVertical="true"    android:layout_alignParentRight="true"    android:src="@drawable/ic_launcher" /></RelativeLayout>

activity_main.

<LinearLayout ="http://schemas.android.com/apk/res/android"  ="http://schemas.android.com/tools"  android:layout_width="match_parent"  android:layout_height="match_parent"  android:orientation="horizontal" >  <com.example.wheelviewdemo.CycleWheelView    android:id="@+id/cycleWheelView"    android:layout_width="0dp"    android:layout_height="wrap_content"    android:layout_weight="1" >  </com.example.wheelviewdemo.CycleWheelView>    <com.example.wheelviewdemo.CycleWheelView    android:id="@+id/cycleWheelView1"    android:layout_width="0dp"    android:layout_height="wrap_content"    android:layout_weight="1" >  </com.example.wheelviewdemo.CycleWheelView>  <com.example.wheelviewdemo.CycleWheelView    android:id="@+id/cycleWheelView2"    android:layout_width="0dp"    android:layout_height="wrap_content"    android:layout_weight="1" >  </com.example.wheelviewdemo.CycleWheelView>  </LinearLayout>