Android — 自定义View(一)之基础知识

前言

记得之前写过一篇自定义View的文章,当时也是尝尝鲜。今天看到一篇自定义View系列文章,也是蛮新的,决定要认认真真的系统学习一遍。原文地址:http://www.gcssloop.com/customview/CustomViewIndex/ 当然,系列中有些东西我觉得很没必要,我想学最实用的知识。(可能我的看法有些片面)还有文章中讲的有些我不明白的地方,我在看看别的博客在加上自己的理解写了这篇博客。这篇文章再详细记录一下学习中非常重要的知识点和要注意的东西。整体目录如下:

  1. View坐标系
  2. 自定义View绘制流程概述
  3. 总结流程关键字以及作用

那准备好了吗?

View 坐标系

首先要明确的是:Android中的坐标系统是以左上角为坐标原点,向右是X轴正方向,向下是Y轴正方向。View的坐标系统是相对于父控件而言的。

View 获取自身宽高:

  • getHeight() 获取View自身高度
  • getWidth() 获取View自身宽度

View 自身坐标:

  • getTop() 获取View自身顶边到其父布局顶边的距离
  • getLeft() 获取View自身左边到其父布局左边的距离
  • getRight() 获取View自身右边到其父布局左边的距离
  • getBottom() 获取View自身底边到其父布局顶边的距离

MotionEvent提供的方法:

  • getX() 获取点击事件距离控件左边的距离,即视图坐标
  • getY() 获取点击事件距离控件顶边的距离,即视图坐标
  • getRawX() 获取点击事件距离整个屏幕左边距离,即绝对坐标
  • getRawY() 获取点击事件距离整个屏幕顶边的距离,即绝对坐标

说了那么多,还是来一图胜千言吧。

自定义View绘制流程

盗一张图:

几个非常重要的函数:

  • 构造函数

    构造函数是View的入口,可以用于初始化一些内容,和获取自定义属性。View的构造函数有四种重载,而有四个参数的构造函数在API21的时候才添加上,暂不考虑。而有三个参数的构造方法中第三个参数是默认的Style,这里默认的Style是指它在当前Application或Activity所用Theme中的默认Style,且只有在明确调用的时候才会生效。

    需要注意的是,即使你在View中使用了Style这个属性也不会调用三个参数的构造方法,所以调用的依旧是两个参数的构造方法。

    调用一个参数的构造方法:

    1
    CustomView view = new CustomView(this);

    调用两个参数的构造方法:

    1
    2
    3
    4
    //在layout文件中 - 格式为: 包名.View名
    <top.omooo.CustomView
    android:layout_width"wrap_content"
    android:layout_height"wrap_content"/>
  • onMeasure() 测量View大小

    View的大小不仅由自身所决定,同时也会受到父控件的影响,为了我们的控件能更好的适应各种情况,一般我们会自己进行测量。

1
2
3
4
5
6
7
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//获取宽度的确切数值
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
//获取宽度的测量模式
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
}
onMeasure()函数中有两个参数,很显然它们是和宽高相关的,**但是实际上它们不是宽和高** ,而是由宽高和各自方向上对应的测量模式来合成的一个值。 测量模式一共有三种,被定义在Android中的View类的一个内部类View.MeasureSpec中。 注意,如果对View的宽高进行修改了,不要调用super.onMeasure();而是调用setMeasuredDimension() 这个函数。
  • onSizeChanged() 确定View的大小

    为什么View的大小测量完了,还要再次确定View的大小呢?这是因为View的大小不仅由View本身控制,而且还受父控件的影响。所以我们在确定View的大小的时候最好使用系统提供的onSizeChanged()回调函数。

1
2
3
4
5
6
7
8
9
10
/**
* @param w View最终的宽
* @param h View最终的高
* @param oldw 上一次宽
* @param oldh 上一次高
*/
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
}
  • onLayout() 确定子View布局位置

    在自定义ViewGroup中的时候,会用它确定子View的位置。onLayout() 一般是循环取出子View,然后计算各个子View的坐标值,然后用以下函数设置子View的位置。

1
2
3
4
5
6
/**
* @param l getLeft()
* @param t getTop()
* @param r getRight()
* @param b getBottom()
*/
@Override public void layout(int l, int t, int r, int b) { super.layout(l, t, r, b); } ​
  • onDraw() 绘制内容

    是实际绘制的部分,使用的是Canvas绘图。

1
2
3
4
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
}
  • 另外

    自定义View之后,一般会对外暴露一些接口,用于控制View的状态或监听View的变化等等。

总结

步骤 关键字 作用
1 构造函数 View初始化
2 onMeasure() 测量View大小
3 onSizeChanged() 确定View大小
4 onLayout() 确定子View布局
5 onDraw() 实际绘制内容
6 提供接口 控制View或监听View状态

Android — 序列化的两种方式

前言

在看一些面试题的时候,竟然把序列化划分为基础知识。真是没接触就不知道它有多简单!本篇就来揭开序列化的神秘面纱,目录如下:

  1. 序列化是什么?
  2. 为什么要进行序列化?
  3. Android中的两种序列化方式概况
  4. Serializable 和 Parcelable 的区别
  5. Serializable 的使用
  6. Parcelable 的使用
  7. 更多参考

国际惯例,先放张图,QwQ

more >>


Android — 事件分发机制(二)

前言

上一篇我们其实已经知道事件分发机制的大概内容了,也是参考了郭大叔的两篇博文总结的。但是隐约感觉有点乱,要是真自己说说事件分发机制,又似乎无从谈起。本篇文章则是侧重总结性,在参考一些较新的博文来慢慢阐述,与上一篇最大的不同的是,本篇还是很建议大家阅读的,虽然我很菜。

参考以下:

安卓自定义View进阶-事件分发机制原理

安卓自定义View进阶-事件分发机制详解

事件分发、拦截与消费

类型 相关方法 Activity ViewGroup View
事件分发 dispatchTouchEvent()
事件拦截 onInterceptTouchEvent()
事件消费 onTouchEvent()

从上表可以看出,Activity和View都是没有事件拦截功能的,这是因为:

  • Activity作为原始的事件分发者,如果Activity拦截了事件就会导致整个屏幕都无法相应事件,那么之前给控件注册事件就显得画蛇添足。
  • View作为事件传递的最末端,要么消费掉事件,要么不处理进行回传,根本没必要进行事件拦截。

事件分发流程

事件收集之后最先传递给Activity,然后依次向下传递,大致如下:

1
Activity --> PhoneWindow --> DecorView --> ViewGroup --> ··· --> View

如果最后分发给了View,View也没有处理事件会怎么办?那么事件就会反方向回传,最终传給Activity,如果最后Activity也没有处理,那么事件才会被抛弃:

1
Activity <-- PhoneWindow <-- DecorView <-- ViewGroup <-- ··· <-- View

这就是非常经典的责任链模式 ,如果我能处理就拦截下来自己干,如果处理不了就交给责任链中的下一个对象。

常见事件

先了解一下常见的几种事件,根据面向对象的思想,事件都被封装成了MotionEvent对象,本篇重点不在于此,所以只会涉及几个与手指触摸相关的常见事件:

时间 简介
ACTION_DOWN 手指 初次接触到屏幕 时触发
ACTION_MOVE 手指 在屏幕上滑动 时触发,往往会触发多次
ACTION_UP 手指 离开屏幕 时触发
ACTION_CANCEL 时间 被上层拦截 时触发

事件分发、拦截与消费

类型 相关方法 ViewGroup View
事件分发 dispatchTouchEvent()
事件拦截 onInterceptTouchEvent()
事件消费 onTouchEvent()

dispatchTouchEvent() 是事件分发机制的核心,所有的事件调度都归他管。ViewGroup有很多子View需要管理,需要事件分发也说得过去,但是为什么View也要有事件分发呢?

那是因为View可以注册很多事件监听器:onClick()、onLongClick()、onTouch(),并且View自身也有onTouchEvent()方法,那么多事件方法肯定要有dispatchTouchEvent()来管理咯。

事件调度顺序:

ViewGroup相关

  1. ViewGroup中可能有多个ChildView,如何判断应该分配给哪一个呢?

    把所有ChildView遍历一遍,如果手指触摸点在ChildView区域内就分给这个View。

  2. 当该点的ChildView有重叠时应该如何分配呢?

    当ChildView重叠时,一般会分配给显示在最上面的ChildView。那如何判断哪个是显示在最上面的呢?后面加载的一般会覆盖掉之前的,所以显示在最上面的是最后加载的。

  3. ViewGroup和ChildView同时注册了事件监听器,哪个会先执行?

    事件优先给ChildView,会被ChildView消费掉,ViewGroup不会相应。

  4. 所有事件都应该被同一View消费。

    同一次点击事件只能被一个View消费。View中onClick事件需要同时接受到ACTION_DOWN和ACTION_UP才能触发,如果分配给了不同的View,那么onClick将无法被正确触发。

    Android为了保证所有的事件都是被一个View消费的,对第一次事件(ACTION_DOWN)进行了特殊判断,View只有消费了ACTION_DOWN事件,才能接受到后续的事件(可点击控件默认会消费所有事件),并且会将后续所有事件传递过来,不会再传递给其他View,除非上层View进行了拦截。如果上层View拦截了当前正在处理的事件,会收到一个ACTION_CANCEL,表示当前事件已经结束,后续事件不会再传递过来。

  5. 一次触摸流程中产生事件应被同一个View消费,全部接受或者全部拒绝。


Android — 事件分发机制(一)

前言

事件分发机制对于Android开发人员可以说是一个非常敏感的词汇,现在网上关于事件分发机制的讲解很多都是参考郭大叔的博客以及《Android开发艺术探索》。所以学习事件分发机制还是推荐看一下博文:

Android事件分发机制完全解析,带你从源码的角度彻底理解(上)

Android事件分发机制完全解析,带你从源码的角度彻底理解(下)

强烈希望大家看以上博文,而忽略本文。

本菜鸡只是记录一下学习过程中比较重要的知识点。

more >>


Android — 开发中的正确姿势

前言

Android开发中要注重的细节很多,本文则是来总结一下开发过程中非常实用的小技巧。我都是站在巨人的肩膀上才能了解这些技巧的存在,写作本文的灵感来源于Frank Lee的博文:Android 开发中的那些正确姿态 。 文章中有些知识讲的并不是很仔细,我则按照他的思路在学习实践并总结一下,目录如下:

  1. Android 布局优化之< include >、< merge >、ViewStub
  2. 使用LinearLayout自带的分割线divider属性来代替1dp View方案
  3. 使用 android.support.v4.widget.Space轻量级控件来占据一定的空间
  4. 使用shape减少图片资源占比以及色彩渐变的坑
  5. LinearLayout 中 android:weightSum 和 android:layout_weight 一起使用更配哦。
  6. 使用 android:descendantFocusability 解决 ViewGroup 与 Children View 之间的焦点占用问题。
  7. 使用 android:fillViewport=”true” 可以将 ScrollView 填满屏幕
  8. Elevation 实现控件阴影效果

more >>

我们一直都向往,面朝大海,春暖花开。 但是几人能做到,心中有爱,四季不败?