Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android自定義控件實現可左右滑動的導航條

Android自定義控件實現可左右滑動的導航條

編輯:關於Android編程

先上效果圖:

這個控件其實算是比較輕量級的,相信不少小伙伴都能做出來。因為項目中遇到了一些特殊的定制要求,所以就自己寫了一個,這裡放出來。 
首先來分析下這個控件的功能: 
•能夠響應左右滑動,並且能響應快速滑動
•選擇項和未選擇項有不同的樣式表現,比如前景色,背景色,字體大小變粗之內的
•在切換選項的時候,如果當前選項未完全呈現在界面前,則自動滾動直至當前選項完全暴露顯示
前兩條還有,簡簡單單就實現了,主要是第三點,這才是我自定義這個控件的原因!那麼如果要實現這個控件,需要用到哪些知識呢? 
•用Scroller來實現控件的滾動
•用VelocityTracker來實現控件的快速滾動 

如果上面兩種技術你都已經會了,那麼我們就可以開始講解代碼了。首先是一些屬性的Getter/Setter方法,這裡采用的鏈式設置法: 

 public IndicatorView color(int colorDefault, int colorSelected, int colorBg){
  this.colorDefault = colorDefault;
  this.colorSelected = colorSelected;
  this.colorBg = colorBg;
  return this;
 }

 public IndicatorView textSize(int textSize){
  this.textSize = textSize;
  return this;
 }

 public IndicatorView text(String[] texts){
  this.texts = texts;
  return this;
 }

 public IndicatorView padding(int[] padding){
  this.padding = padding;
  return this;
 }

 public IndicatorView defaultSelect(int defaultSelect){
  this.selectItem = defaultSelect;
  return this;
 }

 public IndicatorView lineHeight(int lineHeight){
  this.lineHeight = lineHeight;
  return this;
 }

 public IndicatorView listener(OnIndicatorChangedListener listener){
  this.listener = listener;
  return this;
 }

 public IndicatorView type(Type type){
  this.type = type;
  return this;
 }

這裡我們將每一個選項抽象成了一個Item類: 

 public class Item {
  String text;
  int colorDefault;
  int colorSelected;
  int textSize;
  boolean isSelected = false;
  int width;
  Point drawPoint;
  int[] padding = new int[4];
  Rect rect = new Rect();
 }

 然後是控件的初始化操作,主要根據當前控件的寬高,以及設置的一些屬性,進行Item選項的初始化: 

 @Override
 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
  width = MeasureSpec.getSize(widthMeasureSpec);
  height = MeasureSpec.getSize(heightMeasureSpec);
  //初始化Item
  initItems();
  super.onMeasure(widthMeasureSpec, heightMeasureSpec);
 }

 private void initItems(){
  items.clear();
  measureWidth = 0;
  for(int i = 0; i < texts.length; i++){
   Item item = new Item();
   item.text = texts[i];
   item.colorDefault = colorDefault;
   item.colorSelected = colorSelected;
   item.textSize = textSize;
   for(int j = 0; j < item.padding.length; j++){
    item.padding[j] = padding[j];
   }
   mPaint.setTextSize(item.textSize);
   item.width = (int)mPaint.measureText(item.text);
   int dx = 0;
   if(i - 1 < 0){
    dx = 0;
   }else{
    for(int j = 0; j < i; j++){
     dx += items.get(j).padding[0] + items.get(j).width + items.get(j).padding[2];
    }
   }
   int startX = item.padding[0] + dx;
   Paint.FontMetrics metrics = mPaint.getFontMetrics();
   int startY = (int)(height / 2 + (metrics.bottom - metrics.top) / 2 - metrics.bottom);
   item.drawPoint = new Point(startX, startY);
   //設置區域
   item.rect.left = item.drawPoint.x - item.padding[0];
   item.rect.top = 0;
   item.rect.right = item.drawPoint.x + item.width + item.padding[2];
   item.rect.bottom = height;
   //設置默認
   if(i == selectItem){
    item.isSelected = true;
   }
   measureWidth += item.rect.width();
   items.add(item);
  }
  //重繪
  invalidate();
 }

 接下來是事件處理,邏輯很簡單。在DOWN時間記錄坐標值,在MOVE中處理控件的滾動,在UP中處理滾動超屏時的恢復操作,以及點擊的操作。 

 @Override
 public boolean onTouchEvent(MotionEvent event){
  if(mVelocityTracker == null) {
   mVelocityTracker = VelocityTracker.obtain();
  }
  mVelocityTracker.addMovement(event);
  switch(event.getAction()){
   case MotionEvent.ACTION_DOWN:
    mTouchX = (int)event.getX();
    mTouchY = (int)event.getY();
    mMoveX = mTouchX;
    return true;

   case MotionEvent.ACTION_MOVE:
    if(measureWidth > width){
     int dx = (int)event.getX() - mMoveX;
     if(dx > 0){ // 右滑
      if(mScroller.getFinalX() > 0){
       mScroller.startScroll(mScroller.getFinalX(), mScroller.getFinalY(), -dx, 0);
      }else{
       mScroller.setFinalX(0);
      }
     }else{ //左滑
      if(mScroller.getFinalX() + width - dx < measureWidth){
       mScroller.startScroll(mScroller.getFinalX(), mScroller.getFinalY(), -dx, 0);
      }else{
       mScroller.setFinalX(measureWidth - width);
      }
     }
     mMoveX = (int)event.getX();
     invalidate();
    }
    break;

   case MotionEvent.ACTION_UP:
   case MotionEvent.ACTION_CANCEL:
    if(measureWidth > width){
     mVelocityTracker.computeCurrentVelocity(1000);
     int max = Math.max(Math.abs(mScroller.getCurrX()), Math.abs(measureWidth - width - mScroller.getCurrX()));
     mScroller.fling(mScroller.getFinalX(), mScroller.getFinalY(), (int)-mVelocityTracker.getXVelocity(), (int)-mVelocityTracker.getYVelocity(), 0, max, mScroller.getFinalY(), mScroller.getFinalY());
     //手指抬起時,根據滾動偏移量初始化位置
     if(mScroller.getCurrX() < 0){
      mScroller.abortAnimation();
      mScroller.startScroll(mScroller.getCurrX(), mScroller.getCurrY(), -mScroller.getCurrX(), 0);
     }else if(mScroller.getCurrX() + width > measureWidth){
      mScroller.abortAnimation();
      mScroller.startScroll(mScroller.getCurrX(), mScroller.getCurrY(), measureWidth - width - mScroller.getCurrX(), 0);
     }
    }
    if(event.getAction() == MotionEvent.ACTION_UP){
     int mUpX = (int)event.getX();
     int mUpY = (int)event.getY();
     //模擬點擊操作
     if(Math.abs(mUpX - mTouchX) <= mTouchSlop && Math.abs(mUpY - mTouchY) <= mTouchSlop){
      for(int i = 0; i < items.size(); i++){
       if(items.get(i).rect.contains(mScroller.getCurrX() + mUpX, getScrollY() + mUpY)){
        setSelected(i);
        return super.onTouchEvent(event);
       }
      }
     }
    }
    break;

   default:
    break;
  }
  return super.onTouchEvent(event);
 }

 接下來就是很重要的一段代碼,因為這段代碼,才可以讓未完全顯示的Item選項被選中時自動滾動至完全顯示: 

 public void setSelected(int position){
  if(position >= items.size()){
   return;
  }
  for(int i = 0; i < items.size(); i++){
   if(i == position){
    items.get(i).isSelected = true;
    if(i != selectItem){
     selectItem = i;
     //判斷是否需要滑動到完全可見
     if(mScroller.getCurrX() + width < items.get(i).rect.right){
      mScroller.startScroll(mScroller.getFinalX(), mScroller.getFinalY(), items.get(i).rect.right - mScroller.getCurrX() - width, mScroller.getFinalY());
     }
     if(items.get(i).rect.left < mScroller.getCurrX()){
      mScroller.startScroll(mScroller.getFinalX(), mScroller.getFinalY(), items.get(i).rect.left - mScroller.getCurrX(), mScroller.getFinalY());
     }
     if(listener != null){
      listener.onChanged(selectItem);
     }
    }
   }else{
    items.get(i).isSelected = false;
   }
  }
  invalidate();
 }

 然後就是繪制方法了,相當於完全代理給了Item來實現: 

 @Override
 protected void onDraw(Canvas canvas){
  mPaint.setAntiAlias(true);
  canvas.drawColor(colorBg);
  for(Item item : items){
   mPaint.setTextSize(item.textSize);
   if(item.isSelected){
    if(type == Type.SelectByLine){
     //繪制紅線
     mPaint.setColor(item.colorSelected);
     mPaint.setStyle(Paint.Style.FILL);
     canvas.drawRoundRect(new RectF(item.rect.left, item.rect.bottom - lineHeight, item.rect.right, item.rect.bottom), 3, 3, mPaint);
    }else if(type == Type.SelectByFill){
     //繪制紅色背景
     mPaint.setColor(getContext().getResources().getColor(android.R.color.holo_red_light));
     mPaint.setStyle(Paint.Style.FILL);
     canvas.drawRoundRect(new RectF(item.rect.left + 6, item.rect.top, item.rect.right - 6, item.rect.bottom), item.rect.height() * 5 / 12, item.rect.height() * 5 / 12, mPaint);
    }
    mPaint.setColor(item.colorSelected);
   }else{
    mPaint.setColor(item.colorDefault);
   }
   canvas.drawText(item.text, item.drawPoint.x, item.drawPoint.y, mPaint);
  }
 }

接下來就是怎麼使用這個控件了,布局文件:

 <?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
 android:id="@+id/listView"
 android:layout_width="match_parent"
 android:layout_height="match_parent">

 <cc.wxf.androiddemo.indicator.IndicatorView
  android:id="@+id/indicator"
  android:layout_width="match_parent"
  android:layout_height="38dp" />
</RelativeLayout>

MainActvity中:

package cc.wxf.androiddemo;

import android.content.Context;
import android.content.res.Resources;
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;

import cc.wxf.androiddemo.indicator.IndicatorView;

public class MainActivity extends FragmentActivity {

 private IndicatorView indicatorView;

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  initIndicator();
 }

 private void initIndicator(){
  indicatorView = (IndicatorView)findViewById(R.id.indicator);
  Resources resources = getResources();
  indicatorView.color(resources.getColor(android.R.color.black),
    resources.getColor(android.R.color.holo_red_light),
    resources.getColor(android.R.color.darker_gray))
    .textSize(sp2px(this, 16))
    .padding(new int[]{dip2px(this, 14), dip2px(this, 14), dip2px(this, 14), dip2px(this, 14)})
    .text(new String[]{"電視劇","電影","綜藝","片花","動漫","娛樂","會員1","會員2","會員3","會員4","會員5","會員6"})
    .defaultSelect(0).lineHeight(dip2px(this, 3))
    .listener(new IndicatorView.OnIndicatorChangedListener(){

     @Override
     public void onChanged(int position){

     }
    }).commit();
 }

 public static int dip2px(Context context, float dipValue){
  final float scale = context.getResources().getDisplayMetrics().density;
  return (int)(dipValue * scale + 0.5f);
 }

 public static int sp2px(Context context, float spValue){
  final float scale = context.getResources().getDisplayMetrics().scaledDensity;
  return (int)(spValue * scale + 0.5f);
 }

 @Override
 protected void onDestroy() {
  super.onDestroy();
  indicatorView.release();
 }
}


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

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