Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android自定義實現循環滾輪控件WheelView

Android自定義實現循環滾輪控件WheelView

編輯:關於Android編程

首先呈上Android循環滾輪效果圖:

 

現在很多地方都用到了滾輪布局WheelView,比如在選擇生日的時候,風格類似系統提供的DatePickerDialog,開源的控件也有很多,不過大部分都是根據當前項目的需求繪制的界面,因此我就自己寫了一款比較符合自己項目的WheelView。
首先這個控件有以下的需求
 1、能夠循環滾動,當向上或者向下滑動到臨界值的時候,則循環開始滾動
 2、中間的一塊有一塊半透明的選擇區,滑動結束時,哪一塊在這個選擇區,就選擇這快。
 3、繼承自View進行繪制 

然後進行一些關鍵點的講解: 
1、整體控件繼承自View,在onDraw中進行繪制。整體包含三個模塊,整個View、每一塊的條目、中間選擇區的條目(額外繪制一塊灰色區域)。 
2、通過動態設置或者默認設置的可顯示條目數,在最上和最下再各加入一塊,意思就是一共繪制showCount+2個條目。 
3、當最上面的條目數滑動超過條目高度的一半時,進行動態條目更新:將最下面的條目刪除加入第一個條目、將第一個條目刪除加入最下面的條目。 
4、外界可設置條目顯示數、字體大小、顏色、選擇區提示文字(圖中那個年字)、默認選擇項、padding補白等等。 
5、在onTouchEvent中,得到手指滑動的漸變值,動態更新當前所有的條目。 
6、在onMeasure中動態計算寬度,所有條目的寬度、高度、起始Y坐標等等。 
7、通過當前條目和被選擇條目的坐標,超過一半則視為被選擇,並且滑動到對應的位置。 

下面的是WheelView代碼,主要是計算初始值、得到外面設置的值: 

package cc.wxf.view.wheel;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;

import java.util.ArrayList;
import java.util.List;

/**
 * Created by ccwxf on 2016/3/31.
 */
public class WheelView extends View {

 public static final int FONT_COLOR = Color.BLACK;
 public static final int FONT_SIZE = 30;
 public static final int PADDING = 10;
 public static final int SHOW_COUNT = 3;
 public static final int SELECT = 0;
 //總體寬度、高度、Item的高度
 private int width;
 private int height;
 private int itemHeight;
 //需要顯示的行數
 private int showCount = SHOW_COUNT;
 //當前默認選擇的位置
 private int select = SELECT;
 //字體顏色、大小、補白
 private int fontColor = FONT_COLOR;
 private int fontSize = FONT_SIZE;
 private int padding = PADDING;
 //文本列表
 private List<String> lists;
 //選中項的輔助文本,可為空
 private String selectTip;
 //每一項Item和選中項
 private List<WheelItem> wheelItems = new ArrayList<WheelItem>();
 private WheelSelect wheelSelect = null;
 //手點擊的Y坐標
 private float mTouchY;
 //監聽器
 private OnWheelViewItemSelectListener listener;

 public WheelView(Context context) {
 super(context);
 }

 public WheelView(Context context, AttributeSet attrs) {
 super(context, attrs);
 }

 public WheelView(Context context, AttributeSet attrs, int defStyleAttr) {
 super(context, attrs, defStyleAttr);
 }

 /**
 * 設置字體的顏色,不設置的話默認為黑色
 * @param fontColor
 * @return
 */
 public WheelView fontColor(int fontColor){
 this.fontColor = fontColor;
 return this;
 }

 /**
 * 設置字體的大小,不設置的話默認為30
 * @param fontSize
 * @return
 */
 public WheelView fontSize(int fontSize){
 this.fontSize = fontSize;
 return this;
 }

 /**
 * 設置文本到上下兩邊的補白,不合適的話默認為10
 * @param padding
 * @return
 */
 public WheelView padding(int padding){
 this.padding = padding;
 return this;
 }

 /**
 * 設置選中項的復制文本,可以不設置
 * @param selectTip
 * @return
 */
 public WheelView selectTip(String selectTip){
 this.selectTip = selectTip;
 return this;
 }

 /**
 * 設置文本列表,必須且必須在build方法之前設置
 * @param lists
 * @return
 */
 public WheelView lists(List<String> lists){
 this.lists = lists;
 return this;
 }

 /**
 * 設置顯示行數,不設置的話默認為3
 * @param showCount
 * @return
 */
 public WheelView showCount(int showCount){
 if(showCount % 2 == 0){
  throw new IllegalStateException("the showCount must be odd");
 }
 this.showCount = showCount;
 return this;
 }

 /**
 * 設置默認選中的文本的索引,不設置默認為0
 * @param select
 * @return
 */
 public WheelView select(int select){
 this.select = select;
 return this;
 }

 /**
 * 最後調用的方法,判斷是否有必要函數沒有被調用
 * @return
 */
 public WheelView build(){
 if(lists == null){
  throw new IllegalStateException("this method must invoke after the method [lists]");
 }
 return this;
 }

 @Override
 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
 //得到總體寬度
 width = MeasureSpec.getSize(widthMeasureSpec) - getPaddingLeft() - getPaddingRight();
 // 得到每一個Item的高度
 Paint mPaint = new Paint();
 mPaint.setTextSize(fontSize);
 Paint.FontMetrics metrics = mPaint.getFontMetrics();
 itemHeight = (int) (metrics.bottom - metrics.top) + 2 * padding;
 //初始化每一個WheelItem
 initWheelItems(width, itemHeight);
 //初始化WheelSelect
 wheelSelect = new WheelSelect(showCount / 2 * itemHeight, width, itemHeight, selectTip, fontColor, fontSize, padding);
 //得到所有的高度
 height = itemHeight * showCount;
 super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
 }

 /**
 * 創建顯示個數+2個WheelItem
 * @param width
 * @param itemHeight
 */
 private void initWheelItems(int width, int itemHeight) {
 wheelItems.clear();
 for(int i = 0; i < showCount + 2; i++){
  int startY = itemHeight * (i - 1);
  int stringIndex = select - showCount / 2 - 1 + i;
  if(stringIndex < 0){
  stringIndex = lists.size() + stringIndex;
  }
  wheelItems.add(new WheelItem(startY, width, itemHeight, fontColor, fontSize, lists.get(stringIndex)));
 }
 }

 @Override
 public boolean onTouchEvent(MotionEvent event) {
 switch (event.getAction()){
  case MotionEvent.ACTION_DOWN:
  mTouchY = event.getY();
  return true;
  case MotionEvent.ACTION_MOVE:
  float dy = event.getY() - mTouchY;
  mTouchY = event.getY();
  handleMove(dy);
  break;
  case MotionEvent.ACTION_UP:
  handleUp();
  break;
 }
 return super.onTouchEvent(event);
 }

 /**
 * 處理移動操作
 * @param dy
 */
 private void handleMove(float dy) {
 //調整坐標
 for(WheelItem item : wheelItems){
  item.adjust(dy);
 }
 invalidate();
 //調整
 adjust();
 }

 /**
 * 處理抬起操作
 */
 private void handleUp(){
 int index = -1;
 //得到應該選擇的那一項
 for(int i = 0; i < wheelItems.size(); i++){
  WheelItem item = wheelItems.get(i);
  //如果startY在selectItem的中點上面,則將該項作為選擇項
  if(item.getStartY() > wheelSelect.getStartY() && item.getStartY() < (wheelSelect.getStartY() + itemHeight / 2)){
  index = i;
  break;
  }
  //如果startY在selectItem的中點下面,則將上一項作為選擇項
  if(item.getStartY() >= (wheelSelect.getStartY() + itemHeight / 2) && item.getStartY() < (wheelSelect.getStartY() + itemHeight)){
  index = i - 1;
  break;
  }
 }
 //如果沒找到或者其他因素,直接返回
 if(index == -1){
  return;
 }
 //得到偏移的位移
 float dy = wheelSelect.getStartY() - wheelItems.get(index).getStartY();
 //調整坐標
 for(WheelItem item : wheelItems){
  item.adjust(dy);
 }
 invalidate();
 // 調整
 adjust();
 //設置選擇項
 int stringIndex = lists.indexOf(wheelItems.get(index).getText());
 if(stringIndex != -1){
  select = stringIndex;
  if(listener != null){
  listener.onItemSelect(select);
  }
 }
 }

 /**
 * 調整Item移動和循環顯示
 */
 private void adjust(){
 //如果向下滑動超出半個Item的高度,則調整容器
 if(wheelItems.get(0).getStartY() >= -itemHeight / 2 ){
  //移除最後一個Item重用
  WheelItem item = wheelItems.remove(wheelItems.size() - 1);
  //設置起點Y坐標
  item.setStartY(wheelItems.get(0).getStartY() - itemHeight);
  //得到文本在容器中的索引
  int index = lists.indexOf(wheelItems.get(0).getText());
  if(index == -1){
  return;
  }
  index -= 1;
  if(index < 0){
  index = lists.size() + index;
  }
  //設置文本
  item.setText(lists.get(index));
  //添加到最開始
  wheelItems.add(0, item);
  invalidate();
  return;
 }
 //如果向上滑超出半個Item的高度,則調整容器
 if(wheelItems.get(0).getStartY() <= (-itemHeight / 2 - itemHeight)){
  //移除第一個Item重用
  WheelItem item = wheelItems.remove(0);
  //設置起點Y坐標
  item.setStartY(wheelItems.get(wheelItems.size() - 1).getStartY() + itemHeight);
  //得到文本在容器中的索引
  int index = lists.indexOf(wheelItems.get(wheelItems.size() - 1).getText());
  if(index == -1){
  return;
  }
  index += 1;
  if(index >= lists.size()){
  index = 0;
  }
  //設置文本
  item.setText(lists.get(index));
  //添加到最後面
  wheelItems.add(item);
  invalidate();
  return;
 }
 }

 /**
 * 得到當前的選擇項
 */
 public int getSelectItem(){
 return select;
 }

 @Override
 protected void onDraw(Canvas canvas) {
 //繪制每一項Item
 for(WheelItem item : wheelItems){
  item.onDraw(canvas);
 }
 //繪制陰影
 if(wheelSelect != null){
  wheelSelect.onDraw(canvas);
 }
 }

 /**
 * 設置監聽器
 * @param listener
 * @return
 */
 public WheelView listener(OnWheelViewItemSelectListener listener){
 this.listener = listener;
 return this;
 }

 public interface OnWheelViewItemSelectListener{
 void onItemSelect(int index);
 }
}

然後是每一個條目類,根據當前的坐標進行繪制,根據漸變值改變坐標等:

package cc.wxf.view.wheel;

import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.RectF;

/**
 * Created by ccwxf on 2016/3/31.
 */
public class WheelItem {
 // 起點Y坐標、寬度、高度
 private float startY;
 private int width;
 private int height;
 //四點坐標
 private RectF rect = new RectF();
 //字體大小、顏色
 private int fontColor;
 private int fontSize;
 private String text;
 private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);

 public WheelItem(float startY, int width, int height, int fontColor, int fontSize, String text) {
 this.startY = startY;
 this.width = width;
 this.height = height;
 this.fontColor = fontColor;
 this.fontSize = fontSize;
 this.text = text;
 adjust(0);
 }

 /**
 * 根據Y坐標的變化值,調整四點坐標值
 * @param dy
 */
 public void adjust(float dy){
 startY += dy;
 rect.left = 0;
 rect.top = startY;
 rect.right = width;
 rect.bottom = startY + height;
 }

 public float getStartY() {
 return startY;
 }

 /**
 * 直接設置Y坐標屬性,調整四點坐標屬性
 * @param startY
 */
 public void setStartY(float startY) {
 this.startY = startY;
 rect.left = 0;
 rect.top = startY;
 rect.right = width;
 rect.bottom = startY + height;
 }

 public void setText(String text) {
 this.text = text;
 }

 public String getText() {
 return text;
 }

 public void onDraw(Canvas mCanvas){
 //設置鋼筆屬性
 mPaint.setTextSize(fontSize);
 mPaint.setColor(fontColor);
 //得到字體的寬度
 int textWidth = (int)mPaint.measureText(text);
 //drawText的繪制起點是左下角,y軸起點為baseLine
 Paint.FontMetrics metrics = mPaint.getFontMetrics();
 int baseLine = (int)(rect.centerY() + (metrics.bottom - metrics.top) / 2 - metrics.bottom);
 //居中繪制
 mCanvas.drawText(text, rect.centerX() - textWidth / 2, baseLine, mPaint);
 }
}

 最後是選擇項,就是額外得在中間區域繪制一塊灰色區域: 

package cc.wxf.view.wheel;

import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;

/**
 * Created by ccwxf on 2016/4/1.
 */
public class WheelSelect {
 //黑框背景顏色
 public static final int COLOR_BACKGROUND = Color.parseColor("#77777777");
 //黑框的Y坐標起點、寬度、高度
 private int startY;
 private int width;
 private int height;
 //四點坐標
 private Rect rect = new Rect();
 //需要選擇文本的顏色、大小、補白
 private String selectText;
 private int fontColor;
 private int fontSize;
 private int padding;
 private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);

 public WheelSelect(int startY, int width, int height, String selectText, int fontColor, int fontSize, int padding) {
 this.startY = startY;
 this.width = width;
 this.height = height;
 this.selectText = selectText;
 this.fontColor = fontColor;
 this.fontSize = fontSize;
 this.padding = padding;
 rect.left = 0;
 rect.top = startY;
 rect.right = width;
 rect.bottom = startY + height;
 }

 public int getStartY() {
 return startY;
 }

 public void setStartY(int startY) {
 this.startY = startY;
 }

 public void onDraw(Canvas mCanvas) {
 //繪制背景
 mPaint.setStyle(Paint.Style.FILL);
 mPaint.setColor(COLOR_BACKGROUND);
 mCanvas.drawRect(rect, mPaint);
 //繪制提醒文字
 if(selectText != null){
  //設置鋼筆屬性
  mPaint.setTextSize(fontSize);
  mPaint.setColor(fontColor);
  //得到字體的寬度
  int textWidth = (int)mPaint.measureText(selectText);
  //drawText的繪制起點是左下角,y軸起點為baseLine
  Paint.FontMetrics metrics = mPaint.getFontMetrics();
  int baseLine = (int)(rect.centerY() + (metrics.bottom - metrics.top) / 2 - metrics.bottom);
  //在靠右邊繪制文本
  mCanvas.drawText(selectText, rect.right - padding - textWidth, baseLine, mPaint);
 }
 }
}

 源代碼就三個文件,很簡單,注釋也很詳細,接下來就是使用文件了: 

 final WheelView wheelView = (WheelView) findViewById(R.id.wheelView);
 final List<String> lists = new ArrayList<>();
 for(int i = 0; i < 20; i++){
  lists.add("test:" + i);
 }
 wheelView.lists(lists).fontSize(35).showCount(5).selectTip("年").select(0).listener(new WheelView.OnWheelViewItemSelectListener() {
  @Override
  public void onItemSelect(int index) {
  Log.d("cc", "current select:" + wheelView.getSelectItem() + " index :" + index + ",result=" + lists.get(index));
  }
 }).build();

這個控件說簡單也簡單,說復雜也挺復雜,從最基礎的onDraw實現,可以非常高靈活度地定制各自的需求。

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持本站。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved