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

Android自定义可循环的滚动选择器CycleWheelView

程序员文章站 2024-03-05 22:17:31
最近碰到个项目要使用到滚动选择器,原生的numberpicker可定制性太差,不大符合ui要求。 网上开源的wheelview是用scrollview写的,不能循环滚动,...

最近碰到个项目要使用到滚动选择器,原生的numberpicker可定制性太差,不大符合ui要求。
网上开源的wheelview是用scrollview写的,不能循环滚动,而且当数据量很大时要加载的item太多,性能非常低。
然后,还是自己写一个比较靠谱,用的是listview实现的。写完自己体验了一下,性能不错,再大的数据也不怕了。
感觉不错,重新封装了一下,提供了一些接口可以直接按照自己的需求定制,调用方法在mainactivity中。
补个图片: 

Android自定义可循环的滚动选择器CycleWheelView

不多说了,直接上代码:

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.xml: 

<?xml version="1.0" encoding="utf-8"?>
<relativelayout xmlns:android="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.xml: 

<?xml version="1.0" encoding="utf-8"?>
<relativelayout xmlns:android="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.xml: 

<linearlayout xmlns:android="http://schemas.android.com/apk/res/android"
 xmlns:tools="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>

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