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

Android自定义控件实现可左右滑动的导航条

程序员文章站 2024-03-04 17:15:23
先上效果图: 这个控件其实算是比较轻量级的,相信不少小伙伴都能做出来。因为项目中遇到了一些特殊的定制要求,所以就自己写了一个,这里放出来。  首先来分析下...

先上效果图:

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();
 }
}


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