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(),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(),(int)-mVelocityTracker.getXVelocity(),(int)-mVelocityTracker.getYVelocity(),max,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(),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(),items.get(i).rect.right - mScroller.getCurrX() - width,mScroller.getFinalY());
     }
     if(items.get(i).rect.left < mScroller.getCurrX()){
      mScroller.startScroll(mScroller.getFinalX(),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,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.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)})
    .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();
 }
}


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

以上是来客网为你收集整理的Android自定义控件实现可左右滑动的导航条全部内容,希望文章能够帮你解决Android自定义控件实现可左右滑动的导航条所遇到的程序开发问题。

如果觉得来客网网站内容还不错,欢迎将来客网网站推荐给程序员好友。