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

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

(0)

相关推荐

  • Android自定义控件之圆形/圆角的实现代码

    一.问题在哪里? 问题来源于app开发中一个很常见的场景--用户头像要展示成圆的:  二.怎么搞? 机智的我,第一想法就是,切一张中间圆形透明.四周与底色相同.尺寸与头像相同的蒙板图片,盖在头像上不就完事了嘛,哈哈哈! 在背景纯色的前提下,这的确能简单解决问题,但是如果背景没有这么简单呢? 在这种不规则背景下,有两个问题: 1).背景图常常是适应手机宽度缩放,而头像的尺寸又是固定宽高DP的,所以固定的蒙板图片是没法保证在不同机型上都和背景图案吻合的. 2).在这种非纯色背景下,哪天想调整一下头像

  • android开发教程之自定义控件checkbox的样式示例

    主界面xml文件 复制代码 代码如下: <RelativeLayout 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_p

  • Android自定义播放器控件VideoView

    介绍 最近要使用播放器做一个简单的视频播放功能,开始学习VideoView,在横竖屏切换的时候碰到了点麻烦,不过在查阅资料后总算是解决了.在写VideoView播放视频时候定义控制的代码全写在Actvity里了,写完一看我靠代码好乱,于是就写了个自定义的播放器控件,支持指定大小,可以横竖屏切换,手动左右滑动快进快退.好了,下面开始. 效果图有点卡,我也不知道为啥..... VideoView介绍 这个是我们实现视频播放最主要的控件,详细的介绍大家百度就去看,这里介绍几个常用的方法. 用于播放视频

  • Android开发中自定义ProgressBar控件的方法示例

    本文实例讲述了Android开发中自定义ProgressBar控件的方法.分享给大家供大家参考,具体如下: 很简单,首先加载Drawable,在onMeasure设置好其区域大小, 然后使用canvas.clipRect绘图 public class ProgressView extends ImageView { private Drawable maskDraw; /** * 加载的进度 0-100 */ private int mProcess = 20; public ProgressV

  • android自定义倒计时控件示例

    自定义TextView控件TimeTextView代码: 复制代码 代码如下: import android.content.Context;import android.content.res.TypedArray;import android.graphics.Paint;import android.text.Html;import android.util.AttributeSet;import android.widget.TextView; import com.new0315.R;

  • Android控件之RatingBar自定义星级评分样式

    一.RatingBar简单介绍 RatingBar是基于SeekBar(拖动条)和ProgressBar(状态条)的扩展,用星形来显示等级评定,在使用默认RatingBar时,用户可以通过触摸/拖动/按键(比如遥控器)来设置评分, RatingBar自带有两种模式 ,一个小风格 ratingBarStyleSmall,大风格为ratingBarStyleIndicator,大的只适合做指示,不适用与用户交互. 效果图展示: 二.实例 1.布局文件 <?xml version="1.0&qu

  • 轻松实现可扩展自定义的Android滚轮时间选择控件

    项目需求中有个功能模块需要用到时间选择控件,但是android系统自带的太丑了,只能自己优化下,结合WheelView实现滚轮选择日期,好像网上也挺多这种文章的.但是适用范围还是不同,希望这个能够对需求相同的朋友有一定帮助.控件标题还有年月日时分秒这些可以自己控制是否显示,先来看效果. 1.有年月日时分的开始时间 2.只有年月日的结束时间 3.用于有时身份证到期的时间选择(分为勾选长期和直接选择时间两种,另外长期后面自己也可以进行扩展) 4.项目结构 5.直接贴代码,代码里面注释很详细 <spa

  • Android开发之自定义控件用法详解

    本文实例讲述了Android开发之自定义控件用法.分享给大家供大家参考,具体如下: 今天和大家分享下组合控件的使用.很多时候android自定义控件并不能满足需求,如何做呢?很多方法,可以自己绘制一个,可以通过继承基础控件来重写某些环节,当然也可以将控件组合成一个新控件,这也是最方便的一个方法.今天就来介绍下如何使用组合控件,将通过两个实例来介绍. 第一个实现一个带图片和文字的按钮,如图所示: 整个过程可以分四步走.第一步,定义一个layout,实现按钮内部的布局.代码如下: custom_bu

  • Android进度条控件progressbar使用方法详解

    一.简介 二.方法 1)进度条ProgressBar使用方法 1.在layout布局文件中创建ProgressBar控件 <ProgressBar style="?android:attr/progressBarStyleHorizontal" android:layout_width="match_parent" android:layout_height="wrap_content" android:progress="30&

  • android ListView和ProgressBar(进度条控件)的使用方法

    ListView控件的使用:ListView控件里面装的是一行一行的数据,一行中可能有多列,选中一行,则该行的几列都被选中,同时可以触发一个事件,这种控件在平时还是用得很多的.使用ListView时主要是要设置一个适配器,适配器主要是用来放置一些数据.使用起来稍微有些复杂,这里用的是android自带的SimpleAdapter,形式如下:android.widget.SimpleAdapter.SimpleAdapter(Context context, List<? extends Map<

  • Android控件之ProgressBar用法实例分析

    本文实例讲述了Android控件之ProgressBar用法.分享给大家供大家参考.具体如下: ProgressBar位于android.widget包下,其继承于View,主要用于显示一些操作的进度.应用程序可以修改其长度表示当前后台操作的完成情况.因为进度条会移动,所以长时间加载某些资源或者执行某些耗时的操作时,不会使用户界面失去响应.ProgressBar类的使用非常简单,只需将其显示到前台,然后启动一个后台线程定时更改表示进度的数值即可. 以下ProgressBar跟Handle结合,模

随机推荐