Android查缺补漏(View篇)–自定义 View 的骨干流程

View是Android很主要的一部分,常用的View有Button、TextView、艾德itView、ListView、GridView、各类layout等等,开发者通过对这么些View的各样组合以形成丰裕多彩的并行界面,三个用到中界面交互的感受往往在动用的受欢迎程度上起了很首要得效果,所以开发者们大多会设法的做出三个一发特出的界面,例如:通过自定义View、深远学习View的法则以便更好的对其优化使其在操作起来尤其通畅等等,也正因为那样,在面试中View也常常作为面试官重点着眼的靶子之一。

二零一一年的夏日,在Jake的车里,大家停在路边,他问笔者,喜欢什么样音乐。作者工巧地说,不清楚,不限制流派,只要是知足的就行。Jake点点头,这一个话题很难再展开下去。

View是具备控件的基类,包涵Button、TextView、艾德itView等等都一贯或直接接轨自view,View下边还有ViewGroup子类,即LinearLayout、RelativeLayout等都属于ViewGroup。

从1八周岁起,小编就从头干扰,没有兴趣爱好,没有专门痴迷的东西,也尚无杀手锏,怎么做怎么做,好焦虑好焦虑。二十三周岁前时时处在那种担忧中。一无特色,那让本身很窝火。

笔者们供给通晓的是在Android中,无论是View照旧其它界面,右方向表示着x轴的正向,下方向代表着y轴的正向。

27虚岁的春天,1个早晨,笔者坐在床上,低着头看铺在木质感板上的瑜伽垫,薰衣草的花青,发呆。二个想法闪过:你不是直接在著作吗?所以写作是您的兴奋啊。

View 的主导工作原理

在 ActivityThread 中,当Activity被创制后会将 DecorView 添加到 Window
中,同时创设 ViewRootImpl 对象,并将 ViewRootImpl 和 DecorView
建立关联,而 DecorView 便是二个 Activity 的超级View,在三个私下认可的主旨中,它分成标题栏,和内容区域,我们所添加的 View
均是添加到了 DecorView 的内容区域,这个被添加进去的 View
的做事流程专业通过 ViewRootImpl 完毕的。

ViewRoot、DecorView 及 View 的三大流程简介:

  • ViewRoot:对应于 ViewRootImpl,链接 WindowManager 和 DecorView
    的枢纽,View 的三大流程均是通过它成功的。(View 的绘图流程是从
    ViewRoot 的 performTraversals() 方法开头的,它通过
    measure、layout、draw 五个流程最后才能将八个 View 完整的绘图出来。)

  • DecorView:新建一个 Android
    应用时我们都了解,私下认可核心的事态下这一个动用的界面会分为两局地:标题栏、内容区域。而那几个界面包车型大巴头等
    View 就是 DecorView。

  • View的绘图经过了 measure、layout、draw 八个流程:

  1. measure:对应 onMeasure() 方法,测量View的宽、高。
  2. layout:对应 onLayout()
    方法,鲜明view的多少个顶峰,即分明View在父容器中的位置。
  3. draw:对应 onDraw(),绘制View。在自定义 View 时大家也多亏在 onDraw()
    方法内能够在 Canvas 画布上随机的画出我们想要的 View。

以此想法吓了温馨一跳!作者怎么敢相信吗——毫无写作经验,别说公布文章了,高级中学作文课时,同学们都在哗哗哗地写,而4五分钟过后,笔者永久只有四个标题。高校时期在众人网上宣布的日志,都以毫无美感的湍流账,从09年开始写只给本身看的碎碎念。那样的自家,兴趣爱好也可以是编写?而且还开了群众号?

自定义 View

自定义 View 的点子不断一种,能够直接接轨 View,重写 onDraw()
方法,也得以直接接轨
ViewGroup,仍可以持续现有的控件(如:TextView、LinearLayout)等,本篇首要介绍一下直接接轨
View 的措施。

直接接轨 View 来贯彻自定义 View
的那种艺术对比灵敏,能够兑现无数扑朔迷离的成效,那种格局最要害的手续便是重写
onDraw() 方法,通过 Paint 画笔等工具在 Canvas
画布上进展各样图案的绘图以高达我们想要的意义。

实际上在自定义 View
进程中,难题往往不是怎么利用画笔本人,而是绘制出预期效应的笔触,例如:你想通过自定义
View
来做二个折线图控件,传入一组数据怎么规定那么些多少在画布上对应点的相持坐标,而规定点的坐标就供给通过相关的数学公式来计量了,推算出合适的公式往往正是化解难题的关键。

接下去就用那种措施来写个圆形的小 demo 来说Bellamy(Bellamy)下自定义 View 的流水生产线。

  • 新建几个持续 View 的类,添加构造方法,设置 Paint 画笔,重写 onDraw()
    方法,先在画布上以最简易的法子话2个半径为100的圆。

/**
 * 自定义 View 简单示例
 * Created by liuwei on 17/12/14.
 */
public class MyView extends View {

    private final static String TAG = MyView.class.getSimpleName();

    private Paint mPaint = new Paint();
    private int mColor = Color.parseColor("#ff0000");

    public MyView(Context context) {
        super(context);
        Log.i(TAG, "MyView(Context context):content=" + context);
        init();
    }

    public MyView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        Log.i(TAG, "MyView(Context context, @Nullable AttributeSet attrs):content=" + context + " | attrs=" + attrs);
        init();
    }

    private void init() {
        mPaint.setAntiAlias(true); // 消除锯齿
        mPaint.setColor(mColor); // 为画笔设置颜色
    }

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

        // 重写此方法,对自定义控件在 wrap_content 情况下设置默认宽、高
        int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
        int heithtSpecSize = MeasureSpec.getSize(heightMeasureSpec);

        if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) {
            setMeasuredDimension(200, 200);
        } else if (widthSpecMode == MeasureSpec.AT_MOST) {
            setMeasuredDimension(200, heithtSpecSize);
        } else if (heightSpecMode == MeasureSpec.AT_MOST) {
            setMeasuredDimension(widthSpecSize, 200);
        }
    }

    @Override
    protected void onDraw(Canvas canvas) {
        Log.i(TAG, "onDraw: ");
        super.onDraw(canvas);
        canvas.drawCircle(100, 100, 100, mPaint);
    }
}

运营结果就是一个土黄的率真圆,在那个示例中为了使得布局文件中的
wrap_content 平常生效,重写了 onMeasure()
方法,关于这些题材,在那篇博文《Android查缺补漏–自定义 View 中
wrap_content
无效的缓解方案》
中也介绍过了,那里就不多说了。

  • 将地点的圆再扩张一下:做成以画布的可用区域的核心为圆点,画出最大的圆。同时为自定义
    View 设置 padding
    对于1个控件,有 margin 和 padding,margin
    是外间距,属于控件之外的范围,在自定义 View 时不须要对 margin
    做特殊处理。但 padding
    就分裂了,是内距离,须求大家在控件的当中做处理才能让布局文件中对控件设置的
    padding 生效。

private int mPaddingTop;
private int mPaddingBottom;
private int mPaddingLeft;
private int mPaddingRight;

private int mUsableWidth; // 可用宽度(减去padding后的宽度)
private int mUsableHeight;// 可用高度(减去padding后的高度)

private int mUsableStartX = 0; // 画笔起始点的x坐标
private int mUsableStartY = 0; // 画笔其实点的y坐标

private int mCircleX; // 圆心x坐标
private int mCircleY; // 圆心y坐标
private int mCircleRadius;// 圆的半径

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    mPaddingTop = getPaddingTop();
    mPaddingBottom = getPaddingBottom();
    mPaddingLeft = getPaddingLeft();
    mPaddingRight = getPaddingRight();

    // 可用宽度和宽度要考虑padding
    mUsableWidth = getWidth() - mPaddingRight - mPaddingLeft;
    mUsableHeight = getHeight() - mPaddingTop - mPaddingBottom;
    // 画笔起始点要考虑padding
    mUsableStartX = mPaddingLeft;
    mUsableStartY = mPaddingTop;

    // 确定可用区域的中心为圆心
    mCircleX = mUsableStartX + mUsableWidth / 2;
    mCircleY = mUsableStartY + mUsableHeight / 2;

    // 确定圆的半径,以可用宽度和高度两者较短的一半为圆的半径
    if (mUsableWidth <= mUsableHeight) {
        mCircleRadius = mUsableWidth / 2;
    } else {
        mCircleRadius = mUsableHeight / 2;
    }

    canvas.drawCircle(mCircleX, mCircleY, mCircleRadius, mPaint);
}

在布局文件中装置 paddingLeft 为15dp,paddingRight
为30dp,为了更赏心悦目出间距,将控件的背景颜色设为了杏黄,查看效果:

<cn.codingblock.view.reset_view.MyView
        android:id="@+id/myview"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_margin="10dp"
        android:paddingLeft="15dp"
        android:paddingRight="30dp"
        android:background="#000"/>

效果图:
艺术 1

可知,在 onDraw()
方法对padding处理未来,在布局文件中不管怎么设置padding,都能保证圆心在可用区域的主导。

  • 艺术,为自定义 View 添加自定义属性

首先在 res/values 路径下开创1个xml文件,添加3个装置圆的颜色的性格:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="MyView">
        <attr name="circle_color" format="color"/>
    </declare-styleable>
</resources>

在构造方法中分析属性

public MyView(Context context, @Nullable AttributeSet attrs) {
    super(context, attrs);
    TypedArray typeArray = context.obtainStyledAttributes(attrs, R.styleable.MyView);
    mColor = typeArray.getColor(R.styleable.MyView_circle_color, mColor);
    typeArray.recycle();
    init();
}

终极在布局文件中那是性质就足以了,要专注的是,在运用自定义属性时要添加
xmlns:app=”http://schemas.android.com/apk/res-auto” 才可以。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context="cn.codingblock.view.activity.MyViewActivity">

    <cn.codingblock.view.reset_view.MyView
        android:id="@+id/myview"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_margin="10dp"
        android:paddingLeft="15dp"
        android:paddingRight="30dp"
        app:circle_color="#ad42ce"
        android:background="#000"/>

</LinearLayout>

变动颜色后的功力图如下:
艺术 2

不错,那样的自个儿,在创作,并且会平昔写下去。

为自定义View添加交互事件

当今,作者相当领会,小编最爱的音乐流派是民歌、爵士和古典。而自作者的兴趣爱好实在是太常见了,除了狂爱阅读和瑜伽外,任何特殊事物本身都有胃口!我欣赏艺术,笔者欣赏去艺术博物院,作者爱不释手唱歌,作者爱不释手snowshoeing
和tubing(因为不会滑雪),笔者喜欢在家随心所欲地做饮品、咖啡,作者喜爱品尝新饭馆,笔者高兴旅游,小编爱好用手提式有线电话机拍戏,小编爱好欣赏云朵和天上……啊呀,喜欢的事情实在太多了!今后,都数不东山再起了。

MotionEvent 触摸事件

@Override
public boolean onTouchEvent(MotionEvent event) {
    switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            Log.i(TAG, "onTouchEvent: ACTION_DOWN");
            break;
        case MotionEvent.ACTION_UP:
            Log.i(TAG, "onTouchEvent: ACTION_UP");
            break;
        case MotionEvent.ACTION_MOVE:
            Log.i(TAG, "onTouchEvent: ACTION_MOVE");
            break;
    }
    return super.onTouchEvent(event);
}

在自定义 View 中,重写 onTouch伊芙nt() 方法,获取
Motion伊夫nt,正如上边代码所写,Motion伊夫nt 比较常用的轩然大波有三种ACTION_DOWN、ACTION_MOVE、ACTION_UP 分别对应手指按下-移动-离开。

接下去对地点的圆形demo添加八个小事件,正是每当手教导击一下显示器,圆形就肆意换一种颜色:

private Random mRandom = new Random(100);
private int[] mColors = new int[] {
  Color.parseColor("#ff0000"),
  Color.parseColor("#ffffff"),
  Color.parseColor("#ff00ff"),
  Color.parseColor("#ffff00"),
  Color.parseColor("#ff00ff"),
  Color.parseColor("#0000ff")
};

@Override
public boolean onTouchEvent(MotionEvent event) {
    switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            mColor = mColors[mRandom.nextInt(6)];
            mPaint.setColor(mColor);
            invalidate(); // 通知控件重绘
            break;
        case MotionEvent.ACTION_UP:
            Log.i(TAG, "onTouchEvent: ACTION_UP");
            break;
        case MotionEvent.ACTION_MOVE:
            Log.i(TAG, "onTouchEvent: ACTION_MOVE");
            break;
    }
    return super.onTouchEvent(event);
}

效用如下:
艺术 3

大家也足以在此基础上稍加再扩展学一年级下,例如:通过 event.getX() 和
event.getY()
获取触摸点的坐标,判断出点是或不是落在了圆形区域内,从而使只有点手指引到圆形区域内才改变颜色,不然不改变。感兴趣的童鞋可自动入手试一试。

在上边代码中通报 View 重绘时使用了 invalidate() 方法,其实
postInvalidate() 也得以通报 View 重绘,那么那两者有何不相同吗?

事实上一言以蔽之,invalidate() 只幸好 UI 线程中使用,而 postInvalidate()
能够在子线程中应用。

近些年还参与了各样诙谐的、从未听新闻说过的事务,比如闻香写作,闻香画画,接触即兴舞蹈。笔者都好喜欢。将来还要玩怎么吗?不知底,反正作者随着流动走。

ScaleGestureDetector 缩甩手指检测

除去上面最普通的 Motion伊芙nt 事件之外,Android
还提供了重重幽默的事件,就想
GestureDetector(手势检查和测试)、VelocityTracker(速度追踪)等等,用起来也都很有益,其实借使你愿意,这个事件也统统能够在
onTouch伊芙nt() 方法中贯彻,接下去在为上述的圈子 德姆o
添加八个缩放的效能,也正是应用 ScaleGestureDetector
达成,效果跟经常在手提式有线电话机查看照片时我们用两根手指来放大/收缩图片相同。

ScaleGestureDetector
在利用起来也非常的粗略,首先要求初叶化并为其丰裕叁个放缩手势监听器,并且须要在
onTouch伊夫nt() 方法内,通过 ScaleGestureDetector.onTouch伊芙nt(event)
来让 ScaleGestureDetector 接管触摸事件,其他的事项请留心看代码中的注释。

在上述代码的功底上新增如下代码:

private Context mContext;
private ScaleGestureDetector mScaleGestureDetector; // 缩放手势检测
private float mScaleRate = 1; // 缩放比率

private void init() {
    mPaint.setAntiAlias(true); // 消除锯齿
    mPaint.setColor(mColor); // 为画笔设置颜色
    // 初始化 ScaleGestureDetector 并添加缩放手势监听器
    mScaleGestureDetector = new ScaleGestureDetector(mContext, mOnScaleGestureListener);
}

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    mPaddingTop = getPaddingTop();
    mPaddingBottom = getPaddingBottom();
    mPaddingLeft = getPaddingLeft();
    mPaddingRight = getPaddingRight();

    // 可用宽度和宽度要考虑padding
    mUsableWidth = getWidth() - mPaddingRight - mPaddingLeft;
    mUsableHeight = getHeight() - mPaddingTop - mPaddingBottom;
    // 画笔起始点要考虑padding
    mUsableStartX = mPaddingLeft;
    mUsableStartY = mPaddingTop;

    // 确定可用区域的中心为圆心
    mCircleX = mUsableStartX + mUsableWidth / 2;
    mCircleY = mUsableStartY + mUsableHeight / 2;

    // 确定圆的半径,以可用宽度和高度两者较短的一半为圆的半径
    if (mUsableWidth <= mUsableHeight) {
        mCircleRadius = mUsableWidth / 2;
    } else {
        mCircleRadius = mUsableHeight / 2;
    }

    // 让半径乘以缩放倍率
    mCircleRadius *= mScaleRate;
    canvas.drawCircle(mCircleX, mCircleY, mCircleRadius, mPaint);
}

@Override
public boolean onTouchEvent(MotionEvent event) {
    switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            mColor = mColors[mRandom.nextInt(6)];
            mPaint.setColor(mColor);
            invalidate(); // 通知控件重绘
            break;
        case MotionEvent.ACTION_UP:
            Log.i(TAG, "onTouchEvent: ACTION_UP");
            break;
        case MotionEvent.ACTION_MOVE:
            Log.i(TAG, "onTouchEvent: ACTION_MOVE");
            break;
    }

    // 让缩放手势检测器接管触摸事件
    if (mScaleGestureDetector.onTouchEvent(event)) {
        return true;
    }

    return super.onTouchEvent(event);
}

private ScaleGestureDetector.OnScaleGestureListener mOnScaleGestureListener = new ScaleGestureDetector.OnScaleGestureListener() {
    @Override
    public boolean onScale(ScaleGestureDetector detector) {
        Log.i(TAG, "onScale: " + detector.getScaleFactor());
        // 获取缩放比例因子并累乘到缩放倍率上
        mScaleRate *= detector.getScaleFactor();
        postInvalidate();
        return true;
    }

    @Override
    public boolean onScaleBegin(ScaleGestureDetector detector) {
        Log.i(TAG, "onScaleBegin: " + detector.getScaleFactor());
        return true;
    }

    @Override
    public void onScaleEnd(ScaleGestureDetector detector) {
        Log.i(TAG, "onScaleEnd: " + detector.getScaleFactor());
    }
};

艺术 4

地点代码须要小心的是,在 ScaleGestureDetector
捕获到事件后要科学的将事件消费掉(注意代码中回到 true
的地方),不然缩放手势不恐怕寻常干活。

自定义 View 在 Android
中一向以来都以很重点的一局地,在平时的支付想要做出2个个性炫酷的竞相界面是离不开自定义
View,自定义 View
说难不难,说简练也不简单,总之,千里之行,始于足下,只要我们通晓好自定义
View 的基础知识,再繁杂的界面也足以一步步完毕。


最终想说的是,本连串文章为博主对Android知识举行重复梳理,查缺补漏的就学进程,一方面是对本身忘记的事物加以复习重新了然,另一方面相信在重新学习的进度中定会有伟大的新取得,倘使你也有跟自个儿同一的想法,不妨关怀自身一起上学,相互研讨,共同升高!

参考文献:

  • 《Android开发情势探索》
  • 《Android开发进阶从小工到专家》

让潜意识随便画,也很好玩

为此,小编想说的是,不必去拘泥本身是或不是有过人之处,或然有哪些喜好、特长。每种人都有温馨的天然,只怕它还并未显现,可是不用急啊。

我们曾经知道「书中自有黄金屋,书中自有颜如玉」,也总能读到小说讲读书如何转移内在、容颜及生活。这几个你都清楚,可您正是不爱好读书,如何是好吧?大家都算得《高成效职员的多个习惯》是经典,是岁月管理必读,于是你买回来,发现本人并不喜欢,便逼着祥和必须去读,读着读着睡着了,于是你后悔并责骂本人。叁个本得以轻松分享的清晨,变成了一种不欢乐,并在心中刻下3个伤口。

那种业务自身干过,而且不止贰次。年轻的时候作者差了一些就去看了成功学和厚黑学,万幸在买书时,对于人文历史的好奇远当先功利。所以,到近期本身也没读成功学和厚黑学,不禁觉得幸运,好似躲过了「劫数」。

比方你不爱美观书,那就不用看。笔者觉得,你只是没有遇到自个儿喜欢的书罢了。看书,是要等缘分的。比如,有个别书小编买了两三年,才准备好和它们的姻缘。高级中学时代作者就喜爱买书,但从大家小城市新华书店买回来的都是些名著类,后来一本都没读完过。还好,买书的古道热肠还在。

大学时代的二个星期天晚间,笔者在海淀图书城闲逛,看到《素年锦时》,立即棉被服装帧吸引,不加思索地买回去。翻了两页,没有激情,束之高阁,没有愧意。

有个别无聊的深夜,又翻出来,连成一气地读完。抚摸着封面,我才打听到,本身是个装帧孔。原来,从书的封面设计、字体、到纸质再到出版社,笔者都是有供给的。我慢慢领会,小编最兴奋的出版社是三联书店、吉林航空航天学院出版社、译林出版社(排名分先后)。

于是你也不妨多给协调某个苦口婆心。

要了知,每一位都有自个儿的原始。放松、松开地去品味、去做,像流水一样顺利流动,你的原貌自然会议及展览现的。

以此十一在京都品味了接触即兴

众三人以为作者飘着生活,每一日过得如诗如画,不为人间烟火烦心,生活并未琐碎。其实不是。作者一样要为柴米油盐而奔波,只但是小编不供给阿玛尼和Burberry,所以小编得以少奔波一点。小编不供房也不养车,小编把我们吃火锅的钱用来买咖啡,小编把大家买衣裳的钱用来买鲜花和蜡烛,小编买量少但质量高一些的物料,然后用比比皆是年,那盏无印良品的小香薰灯陪伴过本身三年,后来移居赠给外人了,美好和价值仍在继续。

顺手采摘的野花扎成花束送给喜爱的人

随着心流作画,伴着音乐舞蹈,采一小捧野花送给喜欢的人,闻一闻香味,胡乱在剧本上涂抹,用观光者的心态来旅游自身的都会,不须求理由买一束花送给本人,为茶几添上一块桌布,在咖啡里加一小戳儿盐……如诗如画的活着并不须要雷厉风行。

由此,在团结有能力的限量内,去一点一点地把生活过成团结想要的规范吗!而你并不要求中彩票后才早先走动吧。

发表评论

电子邮件地址不会被公开。 必填项已用*标注

网站地图xml地图