Android自定义View实战之角度选择器

本文比较基础,在阅读本文前只需要掌握最基础的自定义View知识和Android事件知识。

起步

有一天晚上,在Google Photos查看照片,用了一下它的图片剪裁功能,于是我马上就被其界面和操作吸引。

第二天我就想模仿做一个这样的裁图库,当然,我做了。同时也做了一个和Google Photos裁图页面几乎一模一样的角度选择器。那么,来看一下最终的效果:

思路

仔细观察这个效果,先分析构成结构,我把它分成三部分:

  1. 表示刻度的点
  2. 相应点上方的数字
  3. 控件中央的当前刻度与三角

可以看出,构成元素十分简单,不涉及图片,Drawable,那么只需要用Canvas画出来就好了。

接下来观察手势的操作,查看随着手指滑动,控件做出的变化,这里的变化有:

  1. 手指按上去时,部分区域变亮(部分区域即为可见区域)
  2. 随着手指滑动,相应的数字发生移动,当前角度值也发生改变
  3. 离中心越近,透明度越低,且的下方的点要大一些

好了,我们对这个控件已经分析的很透彻了,根据分析,首先我们要在ViewonDraw()方法中画出构成元素,之后要让它动起来,在onTouchEvent()方法中监听手势,改变一些值并重绘我们的View,这里很明显,我们要改变的肯定是当前角度这个值。

动手

既然有了思路,那么就要马上动手,不然下次忘记掉了怎么办?

画点

首先,先把点给画出来,位置很简单,肯定是从视图中心开始画,往左往右分别画点。

for (int i = 0; i < mPointCount; i++) {
    canvas.getClipBounds(mCanvasClipBounds);
   canvas.drawPoint(mCanvasClipBounds.centerX() + (i - mPointCount / 2) * mPointMargin,
                    mCanvasClipBounds.centerY(), mPointPaint);
}

其中mPointCount为所要画的点个数,这里默认为51个,mPointMargin为两点间的边距,长度为View的宽度除以点的个数。

看看效果

嗯,成功的把点给画出来了。

画刻度上方的数字

既然把点给画出来了,根据我们的思路,我们要把数字给画到正确的位置上,画数字当然很简单,这里难的是找到正确的位置。

首先,我们的数字是会移动的,随着当前角度的不同,所展示的数字大小位置都不同。但毫无疑问的是,这个位置(x坐标)肯定是关于当前角度的一个函数。至于具体是一个什么样的函数,相信聪明的你很快就可以分析出来,毕竟只是一个线性关系,这里就直接贴代码了。

private void drawDegreeText(int degrees, Canvas canvas, boolean canReach) {
    canvas.drawText(degrees + "°",
                getWidth() / 2 + mPointMargin * degrees / 2 - mTextWidths[0] / 2 * 3 - mCurrentDegrees / 2 * mPointMargin,
                 getHeight() / 2 - 10,
                 mTextPaint);
    }
}

按照我们的效果,我们需要画出-90°~90°的刻度,其中-45°~45°是可到达角度,另外的角度不可到达。

drawDegreeText(0, canvas, true);
drawDegreeText(15, canvas, true);
drawDegreeText(30, canvas, true);
drawDegreeText(45, canvas, true);
drawDegreeText(-15, canvas, true);
drawDegreeText(-30, canvas, true);
drawDegreeText(-45, canvas, true);

drawDegreeText(60, canvas, false);
drawDegreeText(75, canvas, false);
drawDegreeText(90, canvas, false);
drawDegreeText(-60, canvas, false);
drawDegreeText(-75, canvas, false);
drawDegreeText(-90, canvas, false);

好了,来看一下效果,可以看到,数字被画在了正确的位置。

画当前角度

这个超级简单呢,位置也特别好确定,上方三角形的Path也非常好知道,但是这里要注意的是,当当前角度为0°左右时,不应该把0°刻度显示出来。

//画当前角度
if (mCurrentDegrees >= 10) {
    canvas.drawText(mCurrentDegrees + "°", getWidth() / 2 - mTextWidths[0], mBaseLine, mTextPaint);
} else if (mCurrentDegrees <= -10) {
    canvas.drawText(mCurrentDegrees + "°", getWidth() / 2 - mTextWidths[0] / 2 * 3, mBaseLine, mTextPaint);
} else if (mCurrentDegrees < 0) {
    canvas.drawText(mCurrentDegrees + "°", getWidth() / 2 - mTextWidths[0], mBaseLine, mTextPaint);
} else {
    canvas.drawText(mCurrentDegrees + "°", getWidth() / 2 - mTextWidths[0] / 2, mBaseLine, mTextPaint);
}

//三角指示器的Path
mIndicatorPath.moveTo(w / 2, h / 2 + mFontMetrics.top / 2 - 18);
mIndicatorPath.rLineTo(-8, -8);
mIndicatorPath.rLineTo(16, 0);

还有一个小细节,就是离中心越近,其透明度越来越低,也就是说,我们要根据离中心的位置,来改变画笔Paintalpha值,再将点画出。

 for (int i = 0; i < mPointCount; i++) {

     if (i > zeroIndex - 22 && i < zeroIndex + 22 && mScrollStarted) {
         mPointPaint.setAlpha(255);
     } else {
         mPointPaint.setAlpha(100);
     }

     if (i > mPointCount / 2 - 8 && i < mPointCount / 2 + 8
              && i > zeroIndex - 22 && i < zeroIndex + 22) {
         if (mScrollStarted)
              mPointPaint.setAlpha(Math.abs(mPointCount / 2 - i) * 255 / 8);
         else
              mPointPaint.setAlpha(Math.abs(mPointCount / 2 - i) * 100 / 8);
     }
     ……
 }

这时,已经很像了,只是还不能动。

动起来

该绘制的部分我们已经都绘制起来了,是时候让这个View动起来了,角度选择器的触摸事件不复杂,我们只需要在我们移动手指的时候根据滑动距离来改变当前角度值并重绘视图就可以看到移动效果了。为什么呢?因为我们之前在画数字的时候,位置都是由当前角度这个值决定的,所以它一发生变化,数字的位置也会发生改变。

@Override
public boolean onTouchEvent(MotionEvent event) {
    switch (event.getAction()) {
     ……
        case MotionEvent.ACTION_MOVE:
            float distance = event.getX() - mLastTouchedPosition;
            ……
            if (distance != 0) {
                onScrollEvent(event, distance);
            }
            break;
        }
      ……
      return true;
}

private void onScrollEvent(MotionEvent event, float distance) {
    mTotalScrollDistance -= distance;
    postInvalidate();
    mLastTouchedPosition = event.getX();
    mCurrentDegrees = (int) ((mTotalScrollDistance * mDragFactor) / mPointMargin);
    if (mScrollingListener != null) {
        mScrollingListener.onScroll(mCurrentDegrees);
    }
}

当然你还需要处理一些细节性的东西,比如在数字移动的时候,靠近中心一定范围就会消失(透明度变为0),这些都是很容易控制的,只要改变画笔的透明度就好了,但是正是对细节的把控,才能做出一个效果让用户满意的自定义View。最后,再来看一下效果。

扩展

到这里,我们做的角度选择器已经和Google Photos的几乎一模一样了,但是,仅仅这样就够了。不,不够,我们还要继续扩展,为什么只能达到正负45°,我们要所有的范围自由选择,也就是-180°~180°,然后数字颜色啊,点的颜色啊都要让人自由选择。所以我们要扩展我们的角度选择器,把一切可以变化的接口暴露出来。

最后看一下我们多种多样的角度选择器,还是挺好看呀~

总结

这次的自定义View相对于前两次的来说,无疑是简单了很多,只运用了最基础的绘图知识和事件机制,但是看起来效果也还不错哦,嘿嘿,反正我特别喜欢这个角度选择器,虽然我还不知道除了裁图页面可以用到还有哪里可以用到。所以说运用最简单的知识,也可以组合出比较复杂的效果,希望大家多发挥自己的想象力,一起自定义出更多好玩的,实用的,酷炫的控件吧。

希望这篇文章对你有帮助,哪怕只是一些启发,我也很开心了。

最后附上源码地址:https://github.com/wuapnjie/DegreeSeekBar

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,837评论 25 707
  • 声明:原创作品,转载请注明出处 http://www.jianshu.com/p/3b789490fc04 最近,...
    蛇发女妖阅读 10,790评论 31 122
  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 12,066评论 4 62
  • 《如何阅读一本书》的阅读层次方法中,第二个层次是检视阅读,如何快速阅读完一本书。 有系统的略读或粗读;粗浅的阅读。...
    陈sir阅读 797评论 0 4
  • 出售时间之前你要牢记的三条铁律下 昨天读了上,说的是成长才是根本的关键 今天读了铁律二,重视价值忽略估值,在一个大...
    沈青青阅读 183评论 0 1