Android自定义View实现分段选择按钮的实现代码

首先演示下效果,分段选择按钮,支持点击和滑动切换。

视图绘制过程中,要执行onMeasureonLayoutonDraw等方法,这也是自定义控件最常用到的几个方法。
onMeasure:测量视图的大小,可以根据MeasureSpec的Mode确定父视图和子视图的大小。
onLayout:确定视图的位置
onDraw:绘制视图
这里就不做过多的介绍,主要介绍本控件涉及的到的部分。

1.1 获取item大小、起始位置

 @Override
 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  super.onMeasure(widthMeasureSpec, heightMeasureSpec);

  if(isItemZero() || getMeasuredWidth() == 0)
   return;

  mHeight = getMeasuredHeight();
  int width = getMeasuredWidth();
  mItemWidth = (width - 2 * itemHorizontalMargin)/getCount();
  mStart = itemHorizontalMargin + mItemWidth * selectedItem;
  mEnd = width - itemHorizontalMargin - mItemWidth;
 }

1.2 绘制

绘制背景,所有的Item,以及选中项

 @Override
 protected void onDraw(Canvas canvas) {
  super.onDraw(canvas);

  if(isItemZero())
   return;

  drawBackgroundRect(canvas);

  drawUnselectedItemsText(canvas);

  drawSelectedItem(canvas);

  drawSelectedItemsText(canvas);
 }

* 绘制背景区域

背景区域就是个带圆角的长方形

 /**
  * 画背景区域
  * @param canvas
  */
 private void drawBackgroundRect(Canvas canvas) {
  float r = cornersMode == Round?cornersRadius: mHeight >> 1;
  mPaint.setXfermode(null);
  mPaint.setColor(backgroundColor);
  mRectF.set(0, 0, getWidth(), getHeight());
  canvas.drawRoundRect(mRectF, r, r, mPaint);
 }

* 绘制所有未选中Item的文字

轮流绘制所有Item的文字

 /**
  * 画所有未选中Item的文字
  * @param canvas
  */
 private void drawUnselectedItemsText(Canvas canvas) {
  mTextPaint.setColor(textColor);
  mTextPaint.setXfermode(null);
  for (int i = 0; i< getCount(); i++){
   int start = itemHorizontalMargin + i * mItemWidth;
   float x = start + (mItemWidth >> 1) - mTextPaint.measureText(getName(i))/2;
   float y = (getHeight() >> 1) - (mTextPaint.ascent() + mTextPaint.descent())/2;
   canvas.drawText(getName(i), x, y, mTextPaint);
  }
 }

* 绘制选中项

 /**
  * 画选中项
  * @param canvas
  */
 private void drawSelectedItem(Canvas canvas) {
  float r = cornersMode == Round?cornersRadius: (mHeight >> 1) - itemVerticalMargin;
  mPaint.setColor(selectedItemBackgroundColor);
  mRectF.set(mStart, itemVerticalMargin, mStart + mItemWidth, getHeight() - itemVerticalMargin);
  canvas.drawRoundRect(mRectF, r, r, mPaint);
 }

* 绘制选中Item的文字

当选中项移动时,刚移动到下一个Item时,颜色应该是选中的颜色。这里在原来文字之上再画选中Item的文字颜色,就有了被选中的效果。

 /**
  * 画选中Item的文字
  * @param canvas
  */
 private void drawSelectedItemsText(Canvas canvas) {
  canvas.saveLayer(mStart, 0, mStart + mItemWidth, getHeight(), null, Canvas.ALL_SAVE_FLAG);
  mTextPaint.setColor(selectedItemTextColor);
  mTextPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_OUT));
  int begin = mStart/mItemWidth;
  int end = (begin + 2) < getCount()?begin+2:getCount();

  for (int i = begin; i< end; i++){
   int start = itemHorizontalMargin + i * mItemWidth;
   float x = start + (mItemWidth >> 1) - mTextPaint.measureText(getName(i))/2;
   float y = (getHeight() >> 1) - (mTextPaint.ascent() + mTextPaint.descent())/2;
   canvas.drawText(getName(i), x, y, mTextPaint);
  }
  canvas.restore();
 }

1.3 添加手势事件

手势分为三种,ACTION_DOWN、ACTION_MOVE、ACTION_UP,对应动作就是按下,滑动,按起。
当按下时确定按下位置,是在当前Item,则不做处理,当按下位置为其它Item位置,就滑动到其它Item位置。
当手势滑动时,计算相对滑动值,通过改变mStart,改变选中项的位置。
当手势按起时,根据按下位置、速度和方向,判断是否可用移动到下一个Item。

 @Override
 public boolean onTouchEvent(MotionEvent event) {

  if(!isEnabled() || !isInTouchMode() || getCount() == 0)
   return false;

  if (mVelocityTracker == null) {
   mVelocityTracker = VelocityTracker.obtain();
  }
  mVelocityTracker.addMovement(event);

  int action = event.getActionMasked();
  if(action == MotionEvent.ACTION_DOWN){
   x = event.getX();
   onClickDownPosition = -1;
   final float y = event.getY();
   if(isItemInside(x, y)){
    return scrollSelectEnabled;
   }else if(isItemOutside(x, y)){
    if(!mScroller.isFinished()){
     mScroller.abortAnimation();
    }
    onClickDownPosition = (int) ((x - itemHorizontalMargin)/ mItemWidth);
    startScroll(positionStart(x));
    return true;
   }
   return false;
  }else if(action == MotionEvent.ACTION_MOVE){
   if(!mScroller.isFinished() || !scrollSelectEnabled){
    return true;
   }
   float dx = event.getX() - x;
   if(Math.abs(dx) > MIN_MOVE_X){
    mStart = (int) (mStart + dx);
    mStart = Math.min(Math.max(mStart, itemHorizontalMargin), mEnd);
    postInvalidate();
    x = event.getX();
   }
   return true;
  }else if(action == MotionEvent.ACTION_UP){

   int newSelectedItem;
   float offset = (mStart - itemHorizontalMargin)%mItemWidth;
   float itemStartPosition = (mStart - itemHorizontalMargin) * 1.0f/ mItemWidth;

   if(!mScroller.isFinished() && onClickDownPosition != -1){
    newSelectedItem = onClickDownPosition;
   }else{
    if(offset == 0f){
     newSelectedItem = (int)itemStartPosition;
    }else {
     VelocityTracker velocityTracker = mVelocityTracker;
     velocityTracker.computeCurrentVelocity(VELOCITY_UNITS, mMaximumFlingVelocity);
     int initialVelocity = (int) velocityTracker.getXVelocity();

     float itemRate = offset/mItemWidth;
     if (isXVelocityCanMoveNextItem(initialVelocity, itemRate)){
      newSelectedItem = initialVelocity > 0?(int)itemStartPosition+1:(int)itemStartPosition;
     }else {
      newSelectedItem = Math.round(itemStartPosition);
     }
     newSelectedItem = Math.max(Math.min(newSelectedItem, getCount() - 1), 0);
     startScroll(getXByPosition(newSelectedItem));
    }
   }
   onStateChange(newSelectedItem);
   mVelocityTracker = null;
   onClickDownPosition = -1;
   return true;
  }
  return super.onTouchEvent(event);
 }

1.4 保存状态

当手机屏幕方向转换或者内存不足等情况下, 视图会重新加载,这样就会导致状态丢失。使用onSaveInstanceStateonRestoreInstanceState方法保存并恢复状态。

 @Override
 public Parcelable onSaveInstanceState() {
  Parcelable parcelable = super.onSaveInstanceState();
  SelectedItemState pullToLoadState = new SelectedItemState(parcelable);
  pullToLoadState.setSelectedItem(selectedItem);
  return pullToLoadState;
 }

 @Override
 public void onRestoreInstanceState(Parcelable state) {
  if(!(state instanceof SelectedItemState))
   return;
  SelectedItemState pullToLoadState = ((SelectedItemState)state);
  super.onRestoreInstanceState(pullToLoadState.getSuperState());
  selectedItem = pullToLoadState.getSelectedItem();
  invalidate();
 }

想要学习的同学,建议还是直接看项目源码。项目源码地址:https://github.com/danledian/SegmentedControl

到此这篇关于Android自定义View实现分段选择按钮的文章就介绍到这了,更多相关Android自定义View分段选择按钮内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Android自定义View实现微信语音界面

    前言 因为最近的项目需要使用录音功能,开始的想法是Button+OnTouchListener+Dialog实现,在大部分手机中都没问题,只有MI8会偶尔无法触发MotionEvent.ACTION_UP,导致程序异常.所以就自己写了个自定义View来实现,主要也是通过监听 OnTouchListener+Dialog来实现.这里只实现了自定义View,并不涉及录音和播放.效果图如下: 代码 代码并不复杂,配合注释应该很容易理解. /** * Author : BlackHao * Time :

  • Android自定义View实现拼图小游戏

    本文实例为大家分享了Android拼图小游戏的具体代码,供大家参考,具体内容如下 1.效果图: 运行时: 结束时: 2.PuzzleLayoutView: public class PuzzleLayoutView extends RelativeLayout implements View.OnClickListener { //表示将其切成2*2拼图(默认4块) private int mColumn = 2; //容器的内边距 private int mPadding; //每个块块的边距

  • android简单自定义View实现五子棋

    本文实例为大家分享了android自定义View实现五子棋的具体代码,供大家参考,具体内容如下 先说一下吧,android的自定义View就是自己实现一个类去继承View,实现其中的方法,这里面我最感兴趣的就是onDraw方法了,因为你要的样式都要在这里面进实现,看一下效果图吧: 大概就是介个样子的,长得丑不要紧,能用就行,毕竟只是简单的了解一下嘛! *protected void onDraw(Canvas canvas) { super.onDraw(canvas); RectF rectF

  • Android自定义view实现拖拽选择按钮

    本文实例为大家分享了Android实现拖拽选择按钮的具体代码,供大家参考,具体内容如下 github地址:https://github.com/xuezj/DragChooseDemo DragChooseDemo 效果图 Attributes属性(布局文件中的自定义属性) 半径.文字大小.按钮个数注意配合使用,以达到最佳效果 方法 使用 布局文件中的使用 <com.xuezj.dragchooselibrary.view.DragChooseView android:id="@+id/m

  • Android自定义View实现拖动选择按钮

    本文为大家分享了Android实现拖动选择按钮的具体代码,供大家参考,具体内容如下 效果图 View代码 第一步:自定义属性 <declare-styleable name="DragView"> <attr name="icon_drag" format="reference"/> <attr name="color_circle" format="color"/> &

  • Android自定义View实现分段选择按钮的实现代码

    首先演示下效果,分段选择按钮,支持点击和滑动切换. 视图绘制过程中,要执行onMeasure.onLayout.onDraw等方法,这也是自定义控件最常用到的几个方法. onMeasure:测量视图的大小,可以根据MeasureSpec的Mode确定父视图和子视图的大小. onLayout:确定视图的位置 onDraw:绘制视图 这里就不做过多的介绍,主要介绍本控件涉及的到的部分. 1.1 获取item大小.起始位置 @Override protected void onMeasure(int

  • Android自定义View实现字母导航栏的代码

    思路分析: 1.自定义View实现字母导航栏 2.ListView实现联系人列表 3.字母导航栏滑动事件处理 4.字母导航栏与中间字母的联动 5.字母导航栏与ListView的联动 效果图: 首先,我们先甩出主布局文件,方便后面代码的说明 <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/re

  • Android自定义View圆形和拖动圆跟随手指拖动

    单纯的自定义一个圆非常简单 只需要几步就完成 拖动圆添加实现触摸事件即可 我在第一次自定义View圆遇到的小问题: 1.拖动圆的话在xml里面设置的自定义圆的宽和高是它能活动的空间的大小 不是圆控件的大小 如果你定义了100dp 拖动它的时候超过100dp这个距离这个圆就会看不见 就像下面这样 如果想活动于整个屏幕直接给宽和高match_parent属性就好了 2.在布局里自定的view会提示编译 点击Build编译一下就好了 下面开始写代码: 先是单纯的创建一个圆形 创建一个类继承View 实

  • Android自定义view渐变圆形动画

    本文实例为大家分享了Android自定义view渐变圆形动画的具体代码,供大家参考,具体内容如下 直接上效果图 自定义属性 attrs.xml文件 <resources> <declare-styleable name="ProgressRing"> <!--进度起始色--> <attr name="pr_progress_start_color" format="color" /> <!--

  • Android自定义View实现抽奖转盘

    本文实例为大家分享了Android自定义View实现抽奖转盘的具体代码,供大家参考,具体内容如下 public class LuckCircle extends SurfaceView implements SurfaceHolder.Callback,Runnable { private SurfaceHolder mHolder; private Canvas mCanvas; //用于绘制的线程 private Thread mThread; //线程开关的控制 private boole

  • Android自定义view制作抽奖转盘

    本文实例为大家分享了Android自定义view制作抽奖转盘的具体代码,供大家参考,具体内容如下 效果图 TurntableActivity package com.bawei.myapplication.turntable; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.view.MotionEvent; import android.view.View; im

  • Android自定义View绘制彩色圆弧

    本文实例为大家分享了Android自定义View绘制彩色圆弧的具体代码,供大家参考,具体内容如下 效果如下: 自定义View代码如下: package com.example.yan; import android.content.Context; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Rect; import android.graphics.RectF; i

  • Android自定义View实现波浪动画

    本文实例为大家分享了Android自定义View实现波浪动画的具体代码,供大家参考,具体内容如下 效果演示 代码调用与实现效果 xml中调用 <developer.shivam.waveview.Wave android:layout_width="match_parent" android:layout_height="match_parent" app:amplitude="100" app:quadrant="0.5&quo

  • Android自定义view实现滑动解锁效果

    本文实例为大家分享了Android自定义view实现滑动解锁的具体代码,供大家参考,具体内容如下 1. 需求如下: 近期需要做一个类似屏幕滑动解锁的功能,右划开始,左划暂停. 2. 需求效果图如下 3. 实现效果展示 4. 自定义view如下 /** * Desc 自定义滑动解锁View * Author ZY * Mail sunnyfor98@gmail.com * Date 2021/5/17 11:52 */ @SuppressLint("ClickableViewAccessibili

随机推荐