Android自定义控件之可拖动控制的圆环控制条实例代码

前几天收到这么一个需求,本来以为挺简单的,没想到最后发现实现起来还是有点小麻烦的,在这里小小的总结一下。

先看看下面这张需求的样图:

然后在看一下最终实现的效果图,可能是gif录制软件的问题,有一些浮影,忽略就好了:

首先要分析一下最核心的地方,如何获取到滑动距离对应的弧长,看图:

p1是手指按下的点,很明显要想知道当前进度弧边的值,就是要求出角d的值。
以p为圆心点,atan(b)=Math.atan((-p.y)/(-p.x));

所以角d的值为:Math.toDegrees(atan);

那么角b的值就得出来了,b=Math.toDegrees(atan) + mProgressOffest;

图中的圆可以分为四个象限,同理可以得出四个象限中求得弧长的方法:

 /**
   * 更新当前进度对应弧度
   *
   * @param x 按下x坐标点
   * @param y 按下y坐标点
   */
  private void updateCurrentAngle(float x, float y) {
    //根据坐标转换成对应的角度
    float pointX = x - mCenterX;
    float pointY = y - mCenterY;
    float tan_x;//根据左边点所在象限处理过后的x值
    float tan_y;//根据左边点所在象限处理过后的y值
    double atan;//所在象限弧边angle

    //01:第一象限-右上角区域
    if (pointX >= 0 && pointY <= 0) {
      tan_x = pointX;
      tan_y = pointY * (-1);
      atan = Math.atan(tan_x / tan_y);//求弧边
      mCurrentAngle = (int) Math.toDegrees(atan) + 90.f + mProgressOffest;
    }

    //02:第二象限-左上角区域
    if (pointX <= 0 && pointY <= 0) {
      tan_x = pointX * (-1);
      tan_y = pointY * (-1);
      atan = Math.atan(tan_y / tan_x);//求弧边
      mCurrentAngle = (int) Math.toDegrees(atan) + mProgressOffest;
    }

    //03:第三象限-左下角区域
    if (pointX <= 0 && pointY >= 0) {
      tan_x = pointX * (-1);
      tan_y = pointY;
      atan = Math.atan(tan_x / tan_y);//求弧边
      if ((int) Math.toDegrees(atan) >= (90.f - mProgressOffest)) {
        mCurrentAngle = (int) Math.toDegrees(atan) - (90.f - mProgressOffest);
      } else {
        mCurrentAngle = (int) Math.toDegrees(atan) + 270.f + mProgressOffest;
      }
    }

    //04:第四象限-右下角区域
    if (pointX >= 0 && pointY >= 0) {
      tan_x = pointX;
      tan_y = pointY;
      atan = Math.atan(tan_y / tan_x);//求弧边
      mCurrentAngle = (int) Math.toDegrees(atan) + 180.f + mProgressOffest;
    }
  }

获取手指按下的区域,避免误判断:

 /**
   * 按下时判断按下的点是否按在圆环范围内
   *
   * @param x x坐标点
   * @param y y坐标点
   */
  private boolean isTouchArc(float x, float y) {
    double d = getTouchRadius(x, y);
    return d >= mMinValidateTouchArcRadius && d <= mMaxValidateTouchArcRadius;
  }

  /**
   * 计算某点到圆点的距离
   *
   * @param x x坐标点
   * @param y y坐标点
   */
  private double getTouchRadius(float x, float y) {
    float cx = x - getWidth() / 2;
    float cy = y - getHeight() / 2;
    return Math.hypot(cx, cy);
  }

绘制bitmap;

/**
   * 绘制小圆点bitmap
   *
   * @param canvas canvas
   */
  private void drawDragBitmap(Canvas canvas) {
    PointF progressPoint = ChartUtils.calcArcEndPointXY(mCenterX, mCenterY, mRadius,
        mCurrentAngle, 180.f - mProgressOffest);

    int left = (int) progressPoint.x - mDragBitmap.getWidth() / 2;
    int top = (int) progressPoint.y - mDragBitmap.getHeight() / 2;

    //    mBitmapRect = new Rect(left, top, left + mDragBitmap.getWidth(), top +
    //        mDragBitmap.getHeight());
    //
    //    canvas.drawBitmap(mDragBitmap,
    //        new Rect(0, 0, mDragBitmap.getWidth(), mDragBitmap.getHeight()),
    //        mBitmapRect, mBitmapPaint);
    //bitmap直接使用BitmapUtils中的缩放方法缩放,可以不用Rect进行缩放,也可以通过限定Rect来限定bitmap大小
    canvas.drawBitmap(mDragBitmap, left, top, mBitmapPaint);
  }

重写onTouchEvent事件;

@Override
  public boolean onTouchEvent(MotionEvent event) {
    //获取点击位置的坐标
    float x = event.getX();
    float y = event.getY();
    switch (event.getAction()) {
      case MotionEvent.ACTION_DOWN:
        if (isTouchArc(x, y)) {
          mTouchQuadrant = getTouchQuadrant(x, y);
          mIsTouchOnArc = true;
          updateCurrentAngle(x, y);
          return true;
        }
        break;
      case MotionEvent.ACTION_MOVE:
        if (mIsTouchOnArc) {
          updateCurrentAngle(x, y);
          if (mOnCirqueProgressChangeListener != null)
            mOnCirqueProgressChangeListener.onChange(mMinProgress, mMaxProgress,
                Integer.parseInt(mText.replace("℃", "")));
        }
        break;
      case MotionEvent.ACTION_UP:
        mIsTouchOnArc = false;
        mTouchQuadrant = 0;
        if (mOnCirqueProgressChangeListener != null)
          mOnCirqueProgressChangeListener.onChangeEnd(mMinProgress, mMaxProgress,
              Integer.parseInt(mText.replace("℃", "")));
        break;
    }

    invalidate();
    return true;
  }

到这里基本这个自定义控件也就实现完了。但是!是不是!忘了点!什么?没错,就是让我蛋疼不已的圆环上下限值判断。

由于手指滑动的时候,当前的angle值的范围是0-360,因此不可能简单的限定上下限。没有做任何判断的话,在起点处是可以随意滑动的,如下图所示:

很明显这样是不行的,然后就是一阵鸡飞狗跳,简(ou)简(xin)单(li)单(xue)的一阵折腾之后,基本实现了要求,最后更新currentAngle的代码如下:

  /**
   * 更新当前进度对应弧度
   *
   * @param x 按下x坐标点
   * @param y 按下y坐标点
   */
  private void updateCurrentAngle(float x, float y) {
    //根据坐标转换成对应的角度
    float pointX = x - mCenterX;
    float pointY = y - mCenterY;
    float tan_x;//根据左边点所在象限处理过后的x值
    float tan_y;//根据左边点所在象限处理过后的y值
    double atan;//所在象限弧边angle

    //01:第一象限-右上角区域
    //保证dragBitmap在峰值的时候不会因为滑到这个象限更新currentAngle
    if (pointX >= 0 && pointY <= 0) {
      if (((mLastQuadrant == 3 && mLastAngle == 359.f)
          || (mLastQuadrant == 3 && mLastAngle == 0.f))
          && mTouchQuadrant != 1)
        return;

      tan_x = pointX;
      tan_y = pointY * (-1);
      atan = Math.atan(tan_x / tan_y);//求弧边
      mCurrentAngle = (int) Math.toDegrees(atan) + 90.f + mProgressOffest;
      mLastQuadrant = 1;
    }

    //02:第二象限-左上角区域
    if (pointX <= 0 && pointY <= 0) {
      if (((mLastQuadrant == 3 && mLastAngle == 359.f)
          || (mLastQuadrant == 3 && mLastAngle == 0.f))
          && mTouchQuadrant != 2) {
        return;
      }

      tan_x = pointX * (-1);
      tan_y = pointY * (-1);
      atan = Math.atan(tan_y / tan_x);//求弧边
      mCurrentAngle = (int) Math.toDegrees(atan) + mProgressOffest;
      mLastQuadrant = 2;
    }

    //03:第三象限-左下角区域
    if (pointX <= 0 && pointY >= 0) {
      tan_x = pointX * (-1);
      tan_y = pointY;
      atan = Math.atan(tan_x / tan_y);//求弧边
      if ((int) Math.toDegrees(atan) >= (90.f - mProgressOffest)) {
        mCurrentAngle = (int) Math.toDegrees(atan) - (90.f - mProgressOffest);
        if (mLastAngle >= 270.f) {
          mCurrentAngle = 359.f;
        }
      } else {
        mCurrentAngle = (int) Math.toDegrees(atan) + 270.f + mProgressOffest;
        if (mLastAngle <= 90.f) {
          mCurrentAngle = 0.f;
        }
      }
      mLastQuadrant = 3;
    }

    //04:第四象限-右下角区域
    //保证dragBitmap在峰值的时候不会因为滑到这个象限更新currentAngle
    if (pointX >= 0 && pointY >= 0) {
      if (((mLastQuadrant == 3 && mLastAngle == 359.f)
          || (mLastQuadrant == 3 && mLastAngle == 0.f))
          && mTouchQuadrant != 4)
        return;

      tan_x = pointX;
      tan_y = pointY;
      atan = Math.atan(tan_y / tan_x);//求弧边
      mCurrentAngle = (int) Math.toDegrees(atan) + 180.f + mProgressOffest;
      mLastQuadrant = 4;
    }
    mLastAngle = mCurrentAngle;
  }

其实做之前就真的觉得是挺简单的一个自定义控件,结果万万没想到因为最后这么一点代码折腾了半天。虽然最后这坨代码看着确实挺蛋疼的,但是暂时也想不到什么好的方法了,先这样吧。

最后贴上完整代码:

https://github.com/Horrarndoo...

总结

以上所述是小编给大家介绍的Android自定义控件之可拖动控制的圆环控制条实例代码,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对我们网站的支持!

(0)

相关推荐

  • 基于Android自定义控件实现雷达效果

    如何制作出类似雷达扫描的效果,具体方法如下 一.效果图 二.实现思路 1.自定义控件RadarView用来画雷达的效果图,可以自定义属性包括 backgroundColor:背景颜色 circleNum:圆的数量 startColor:开始颜色 endColor:结束颜色 lineColor:线的颜色 2.通过Handler循环发送消息到MessageQueue中,将mRotate加3,使Matrix旋转mRotate,重绘雷达扫描的圆. 3.通过梯度渐变扫描渲染器SweepGradient,在

  • Android自定义控件之日期选择控件使用详解

    Android日期选择控件效果如下: 调用的代码: @OnClick(R.id.btn0) public void btn0() { final AlertDialog dialog = new AlertDialog.Builder(context).create(); dialog.show(); Window window = dialog.getWindow(); window.setContentView(R.layout.dialog_change_date); window.set

  • Android自定义控件通用验证码输入框的实现

    需求 4位验证码输入框: 效果图: 1. 输入框一行可输入4位数字类型的验证码: 2. 4位数字之间有间隔(包括底线): 3. 输入框不允许有光标: 4. 底线根据输入位置显示高亮(蓝色): 6. 输入完成,回调结果,输入过程中,也进行回调: 分析 这种效果,很难直接在Edittext上处理: -- 输入框均分4等份,还要有间隔: -- 更难处理的是Edittext输入框禁止光标,那么,没有光标,我们如何调起虚拟键盘输入数据? -- 等... 与其在一个控件上折腾,这么难受,不如自定义一个控件,

  • Android 下载文件通知栏显示进度条功能的实例代码

    1.使用AsyncTask异步任务实现,调用publishProgress()方法刷新进度来实现(已优化) public class MyAsyncTask extends AsyncTask<String,Integer,Integer> { private Context context; private NotificationManager notificationManager; private NotificationCompat.Builder builder; public M

  • Android自定义控件EditText使用详解

    本文实例为大家分享了Android自定义控件EditText的具体代码,供大家参考,具体内容如下 自定义控件分三种: 1. 自绘控件 2. 组合控件 3. 继承控件 代码已上传到 github 以后的自定义控件就都放这个仓库 需求 这里由于项目的需要实现一个自定义EditText,主要实现的为两点,一个是工具图标toolIcon,例如点击清除EditText内容.一个为EditText左边的提示图标hintIcon, 例如输入账号密码时前面的图标. 为了让这个控件的拓展性更高,设置了两个点击事件

  • android实现音乐播放器进度条效果

    本文实例为大家分享了android实现音乐播放器进度条效果的具体代码,供大家参考,具体内容如下 效果图 依赖3个对象 MediaPlayer:实现音乐播放,暂停,缓冲. SeekBar:滑动的进度条. java.util.Timer:定时器,时时更新进度条. main.xml样式文件 <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android

  • Android实现多段颜色进度条效果

    多段颜色的进度条实现思路,供大家参考,具体内容如下 这个进度条其实相对简单. 这里可以把需要绘制的简单分为两个部分 1.灰色背景部分 2.多段颜色的进度部分 考虑到实际绘制中,分段部分不太容易根据进度值进行动态绘制. 故把多段颜色部分作为背景进行绘制,实际的灰色部分根据进度值变化,达到多段颜色部分进度变化的效果. 实现步骤 1.自定义View 来绘制进度条 2.定义背景及进度条绘制所需的画笔 private Paint backgroundPaint, progressPaint, linePa

  • Android开发之进度条ProgressBar的示例代码

    说明 ProgressBar一般用于显示一个过程,例如数据加载过程,文件下载进度,音乐播放进度等. 默认形式ProgressBar 默认方式下,ProgressBar显示为圆形进度,循环转圈,不显示具体的进度值,控制其显隐藏即可,如下 适用于界面加载 //xml中 <ProgressBar android:layout_width="wrap_content" android:layout_height="wrap_content" /> //代码中控制

  • Android自定义控件开发实战之实现ListView下拉刷新实例代码

    这篇博客为大家介绍一个android常见的功能--ListView下拉刷新: 首先下拉未松手时候手机显示这样的界面: 下面的代码是自定的扎样的控件: <span style="font-family: comic sans ms,sans-serif; font-size: 16px;">package com.dhsr.smartID.view; import android.content.Context; import android.util.AttributeSe

  • Android自定义控件之可拖动控制的圆环控制条实例代码

    前几天收到这么一个需求,本来以为挺简单的,没想到最后发现实现起来还是有点小麻烦的,在这里小小的总结一下. 先看看下面这张需求的样图: 然后在看一下最终实现的效果图,可能是gif录制软件的问题,有一些浮影,忽略就好了: 首先要分析一下最核心的地方,如何获取到滑动距离对应的弧长,看图: p1是手指按下的点,很明显要想知道当前进度弧边的值,就是要求出角d的值. 以p为圆心点,atan(b)=Math.atan((-p.y)/(-p.x)); 所以角d的值为:Math.toDegrees(atan);

  • Android系统音量条实例代码

    最近在定制Android系统音量条,发现代码还是蛮多的,下面总结一下. 代码是基于5.1.1版本的. 系统音量条的代码是在/frameworks/base/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java 布局文件是在/frameworks/base/packages/SystemUI/res/layout下. 先看看原生的音量条样式: 在代码中可以发现volume_dialog.xml这个文件,这个文件就是承载

  • Android 自定义球型水波纹带圆弧进度效果(实例代码)

    需求 如下,实现一个圆形水波纹,带进度,两层水波纹需要渐变显示,且外围有一个圆弧进度. 思路 外围圆弧进度:可以通过canvas.drawArc()实现.由于圆弧需要实现渐变,可以通过给画笔设置shader(SweepGradient)渲染,为了保证圆弧起始的颜色值始终一致,需要动态调整shader的参数.具体参见 SweepGradient(centerX.toFloat(), centerY.toFloat(), circleColors[0], floatArrayOf(0f, value

  • Android开发中通过手机号+短信验证码登录的实例代码

    首先,需要一个电话号码,目前很多账户都是将账户名设置成手机号,然后点击按钮获取手机验证码. 其次,你需要后台给你手机短信的验证接口,各个公司用的不一样,这个身为前端,不需要你来考虑,你只要让你后台给你写好接口,你直接调用就好了. activity_login.xml <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.andr

  • Android自定义View实现带数字的进度条实例代码

    第一步.效果展示 图1.蓝色的进度条 图2.红色的进度条 图3.多条颜色不同的进度条 图4.多条颜色不同的进度条 第二步.自定义ProgressBar实现带数字的进度条 0.项目结构 如上图所示:library项目为自定义的带数字的进度条NumberProgressBar的具体实现,demo项目为示例项目以工程依赖的方式引用library项目,然后使用自定义的带数字的进度条NumberProgressBar来做展示 如上图所示:自定义的带数字的进度条的library项目的结构图 如上图所示:de

  • Android仿泡泡窗实现下拉菜单条实例代码

    功能描述:点击下拉按钮,显示出所有的条目,有删除和点击功能,点击后将条目显示. 注意:泡泡窗默认是没有焦点的.要让泡泡窗获取到焦点.假如listview的item中有Button,ImageButton,CheckBox等会强制获取焦点的view 此时,listview的item无法获取焦点,从而无法被点击 解决方法:给item的根布局增加以下属性 Android:descendantFocusability="blocksDescendants"设置之后,Button获取焦点,ite

  • Android实现listview滑动时渐隐渐现顶部栏实例代码

    我在开发的时候遇到了这样的需求,就是在listview的滑动中,需要对顶部的栏目由透明慢慢的变为不透明的状态,就是以下的效果 最先开始的时候想的很简单,无非就是监听listview的滑动距离,然后根据距离算出透明度的比值,就可以了,但是事实上呢也的确是这样做的 只是在获取listview的滑动距离上可能没法直接获取,需要动态的去计算 下面贴出全部代码吧,不想码字了,最近感冒了,脑袋晕乎乎的,还疼,代码更直观一些 private void initListener() { lvList.setOn

  • Android长按imageview把图片保存到本地的实例代码

    工具类 之前用 AsyncTask 现在改用rxJava public class SaveImageUtils { public static void imageSave(final ImageView imageView, final int id) { Observable .create(new Observable.OnSubscribe<ImageView>() { @Override public void call(Subscriber<? super ImageVie

  • Python实现控制台中的进度条功能代码

    进度条最主要的问题就是所有字符全部在同一行,而且可以修改. 然而当执行print语句的时候,python会在打印完这个语句的同时在结尾加上'\n',也就是换行,这就导致在控制台下一旦被print之后就无法再修改了.所以我们现在的输出就不能再使用print来完成了. 我们要使用的是来自sys库的sys.stdout.write()函数,这个函数会在控制台输出这个字符串的同时不加上任何结尾,这就意味着这个输出还没有完全结束.通过sys.stdout.flush()函数可以把输出暂时打印在控制台中(造

随机推荐