[Android 知识点] 自定义View(一)

View的生命周期

image

View分类

分为继承View与ViewGroup两种。

View的流程

构造函数

绘制初始化,进行各种计算,设定默认值或做任何我们需要的事情。
为了使我们的View更易于使用和配置,Android提供了很有用的AttributeSet接口。它很容易实现,通过静态参数来设置View,对于以后新特性加入或者新屏幕拓展性支持也更好。

  • 首先,创建一个新的文件attrs.xml,自定义View属性都可以放在该文件中。
<resources>
    <declare-styleable name="MyView">
        <attr name="exampleString" format="string"/>
        <attr name="exampleDimension" format="dimension"/>
        <attr name="exampleColor" format="color"/>
        <attr name="exampleDrawable" format="color|reference"/>
    </declare-styleable>
</resources>
  • 引入到xml中
<FrameLayout
    <--引入命名空间-->
    xmlns:app="http://schemas.android.com/apk/res-auto">


    <xxxx.MyView

        app:exampleColor="#33b5e5"
        app:exampleDimension="24sp"
        app:exampleDrawable="@android:drawable/ic_menu_add"
        app:exampleString="Hello, MyView"/>

</FrameLayout>
  • 在代码中调用
  final TypedArray a = getContext().obtainStyledAttributes(
                attrs, R.styleable.MyView, defStyle, 0);
  • 提取属性

提取属性相关API

例如:

mExampleString = a.getString( R.styleable.MyView_exampleString);
mExampleColor = a.getColor(R.styleable.MyView_exampleColor,mExampleColor);
// Use getDimensionPixelSize or getDimensionPixelOffset when dealing with
// values that should fall on pixel boundaries.
mExampleDimension =a.getDimension(R.styleable.MyView_exampleDimension,mExampleDimension);

属性的前缀是:MyView_,不再使用时,调用recycle()方法回收资源。


  • 构造函数的几种形式:

public MyView(Context context) {
        super(context);
        init(null, 0);
    }

public MyView(Context context, AttributeSet attrs) {
    super(context, attrs);
    init(attrs, 0);
}

public MyView(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
    init(attrs, defStyle);
}
  1. 在代码中直接new一个CustomView实例的时候,会调用第一个构造函数
  2. 在xml布局文件中调用CustomView的时候,调用第二个构造函数
  3. 在xml布局文件中调用CustomView指定style的时候,调用第三个构造函数
private void init(AttributeSet attrs, int defStyle) {
    // Load attributes
    final TypedArray a = getContext().obtainStyledAttributes(
            attrs, R.styleable.MyView, defStyle, 0);

    mExampleString = a.getString(
            R.styleable.MyView_exampleString);
    mExampleColor = a.getColor(
            R.styleable.MyView_exampleColor,
            mExampleColor);
    // Use getDimensionPixelSize or getDimensionPixelOffset when dealing with
    // values that should fall on pixel boundaries.
    mExampleDimension = a.getDimension(
            R.styleable.MyView_exampleDimension,
            mExampleDimension);

    if (a.hasValue(R.styleable.MyView_exampleDrawable)) {
        mExampleDrawable = a.getDrawable(
                R.styleable.MyView_exampleDrawable);
        mExampleDrawable.setCallback(this);
    }

    a.recycle();

    // Set up a default TextPaint object
    mTextPaint = new TextPaint();
    mTextPaint.setFlags(Paint.ANTI_ALIAS_FLAG);
    mTextPaint.setTextAlign(Paint.Align.LEFT);

    // Update TextPaint and text measurements from attributes
    invalidateTextPaintAndMeasurements();
}

可以看出,构造函数的职责主要是:
1. 提取xml中的属性
2. 初始化画笔或其他属性

onAttachedToWindow

父View调用addView(View)后,这个View将被依附到一个窗口。在这个阶段,我们的View会知道它被包围的其他view

onMeasure

该阶段决定了自己的大小,当你重写此方法,需要记得的是,在结束时调用setMeasuredDimension(int width, int height)方法。

可以通过xml或者动态设置了具体的大小。

  • 获取你的View MeasureSpec大小和模式,MeasureSpec由32位 int值组成,高2位表示的是测量模式(specMode),后面的30位代表的是测量的大小(specSize)。

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
    }
  • MeasureSpec.getSize()会解析MeasureSpec值得到父容器width或者height

  • MeasureSpec.getMode()会得到三个int类型的值分别为:

  1. MeasureSpec.EXACTLY : 适配Match_Parent , 或指定大小的时候
  2. MeasureSpec.AT_MOST : 匹配wrap_content
  3. MeasureSpec.UNSPECIFIED :未定义
//根据不同模式选择大小
private int measureHanlder(int measureSpec, int defaultSize) {
    int result = defaultSize;
    int specMode = MeasureSpec.getMode(measureSpec);
    int specSize = MeasureSpec.getSize(measureSpec);
        //适配Match_Parent , 或指定大小的时候
        if (specMode == MeasureSpec.EXACTLY) {
            result = specSize;
        }
        //适配wrap_content
        else if (specMode == MeasureSpec.AT_MOST) {
            //默认大小
            result = Math.min(defaultSize, specSize);
        }
        return result;
    }

■ 假设:

android:layout_width="300dp"
android:layout_height="300dp"

注:density值表示每英寸有多少个显示点,与分辨率是两个概念。

■ 则实际大小与设备有关:

image

  • drawable-mdpi: 屏幕密度Density为160的手机设备(在此设备上,1dp = 1px)
  • drawable-hdpi: 屏幕密度Density为240的手机设备
  • drawable-xhdpi: 屏幕密度Density为320的手机设备
  • drawable-xxhdpi: 屏幕密度Density为480的手机设备

转化系数分别为:1 , 1.5 , 2 , 3

■ 可以通过代码识别屏幕:

DisplayMetrics dm = new DisplayMetrics();
dm = getResources().getDisplayMetrics();

float density = dm.density; 
// 屏幕密度(像素比例:0.75/1.0/1.5/2.0)
int densityDPI = dm.densityDpi; 
// 屏幕密度(每寸像素:120/160/240/320)
float xdpi = dm.xdpi;
float ydpi = dm.ydpi;

int screenWidth = dm.widthPixels; // 屏幕宽(像素,如:480px)
int screenHeight = dm.heightPixels; // 屏幕高(像素,如:800px)
Log.i(TAG, "onCreate: x" + screenWidth + " y " + screenHeight + "density:" + density);

onLayout

image
因为我们现在讨论的是View,没有子View需要排列,所以这一步其实我们不需要做额外的工作。对ViewGroup类,onLayout方法中,我们需要将所有子View的大小宽高设置好

onDraw

image
在这里,使用Canvas和Paint对象你将可以画任何你需要的东西,onDraw的参数Canvas,它一般用于绘制不同形状,而Paint对象定义形状颜色。简单地说,Canvas用于绘制对象,而Paint用于造型。

使自定义View,要始终牢记onDraw会花费大量的时间。当布局有一些变化,滚动、快速滑动都会导致重新绘制。避免在onDraw中进行对象分配的操作,对象应该只创建一次并在将来重用。

Canvas相关API

关于画笔与画布API的使用,另起文章介绍

View更新

从View的生命周期图可以得知,可以重绘View自身有两种方法。invalidate()和requestLayout()方法会帮助你在运行时动态改变View状态

  • invalidate()方法,用来简单重绘View。例如更新其文本,色彩或触摸交互性。View将只调用onDraw()方法再次更新其状态。
  • requestLayout()方法,你可以看到其将会从`onMeasure()开始更新View。这意味着你的View更新后,它改变它的大小,你需要再次测量它,并依赖于新的大小来重新绘制。
  • forceLayout()方法,标识View在下一次重绘,需要重新调用layout过程。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值