自定义View系列教程05--示例分析

自定义View系列教程01–常用工具介绍

自定义View系列教程02–onMeasure源码详尽分析

自定义View系列教程03–onLayout源码详尽分析

自定义View系列教程04–Draw源码分析及其实践

自定义View系列教程05–示例分析

PS:如果觉得文章太长,那就直接看视频



之前结合源码分析完了自定义View的三个阶段:measure,layout,draw。

那么,自定义有哪几种常见的方式呢?

  1. 直接继承自View

    在使用该方式实现自定义View时通常的核心操作都在onDraw( )当中进行。但是,请注意,在分析measure部分源码的时候,我们提到如果直接继承自View在onMeasure( )中要处理view大小为wrap_content的情况,否则这种情况下的大小和match_parent一样。除此以为,还需要注意对于padding的处理。

  2. 继承自系统已有的View

    比如常见的TextView,Button等等。如果采用该方式,我们只需要在系统控件的基础上做出一些调整和扩展即可,而且也不需要去自己支持wrap_content和padding。

  3. 直接继承自ViewGroup

    如果使用该方式实现自定义View,请注意两个问题

    第一点:

    在onMeasure( )实现wrap_content的支持。这点和直接继承自View是一样的。

    第二点:

    在onMeasure( )和onLayout中需要处理自身的padding以及子View的margin

  4. 继承自系统已有的ViewGroup

    比如LinearLayout,RelativeLayout等等。如果采用该方式,那么在3中提到的两个问题就不用再过多考虑了,简便了许多。

在此,举两个例子。



瞅瞅第一个例子,效果如下图:

对于该效果的主要描述如下:

  1. 点击Title部分,展开图片
  2. 再次点击Title,收缩图片
  3. 图片的收缩和展开都渐次进行的,并使用动画切换右侧箭头的方向。

好了,效果已经看到了,我们来明确和拆解一下这个小功能

  1. 控件由数字,标题,箭头,图片四部分组成
  2. 点击标题逐渐地显示或隐藏图片
  3. 在图片的切换过程中伴随着箭头方向的改变

弄清楚这些就该动手写代码了。

先来看这个控件的布局文件

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="#ffffff"
    android:orientation="vertical">

    <RelativeLayout
        android:id="@+id/titleRelativeLayout"
        android:padding="30px"
        android:layout_width="match_parent"
        android:layout_height="170px"
        android:clickable="true">

        <TextView
            android:id="@+id/numberTextView"
            android:layout_width="70px"
            android:layout_height="70px"
            android:gravity="center"
            android:layout_centerVertical="true"
            android:background="@drawable/circle_textview"
            android:clickable="false"
            android:text="1"
            android:textStyle="bold"
            android:textColor="#EBEFEC"
            android:textSize="35px" />

        <TextView
            android:id="@+id/titleTextView"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_centerVertical="true"
            android:layout_toRightOf="@id/numberTextView"
            android:layout_marginLeft="30px"
            android:clickable="false"
            android:textColor="#1d953f"
            android:textSize="46px" />

        <ImageView
            android:id="@+id/arrowImageView"
            android:layout_width="48px"
            android:layout_height="27px"
            android:layout_alignParentRight="true"
            android:layout_centerVertical="true"
            android:background="@drawable/btn_an_xxh"
            android:clickable="false"
            android:scaleType="fitCenter" />
    </RelativeLayout>

    <View
        android:layout_width="match_parent"
        android:layout_height="2px"
        android:layout_below="@id/titleRelativeLayout"
        android:background="#E7E7EF"
        android:clickable="false"
        />

    <RelativeLayout
        android:id="@+id/contentRelativeLayout"
        android:visibility="gone"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content">
    </RelativeLayout>

</LinearLayout>

请注意,在此将显示图片的容器即contentRelativeLayout设置为gone。

为什么要这么做呢?因为进入应用后是看不到图片部分的,只有点击后才可见。嗯哼,你大概已经猜到了:图片的隐藏和显示是通过改变容器的visibility实现的。是的!那图片的逐渐显示和隐藏还有箭头的旋转又是怎么做的呢?请看该控件的具体实现。

package com.stay4it.testcollapseview;

import android.content.Context;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.LayoutInflater;
import android.view.View;
import android.view.WindowManager;
import android.view.animation.Animation;
import android.view.animation.Transformation;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;
/**
 * 原创作者:
 * 谷哥的小弟
 *
 * 博客地址:
 * http://blog.csdn.net/lfdfhl
 */
public class CollapseView extends LinearLayout {
    private long duration = 350;
    private Context mContext;
    private TextView mNumberTextView;
    private TextView mTitleTextView;
    private RelativeLayout mContentRelativeLayout;
    private RelativeLayout mTitleRelativeLayout;
    private ImageView mArrowImageView;
    int parentWidthMeasureSpec;
    int parentHeightMeasureSpec;
    public CollapseView(Context context) {
        this(context, null);
    }

    public CollapseView(Context context, AttributeSet attrs) {
        super(context, attrs);
        mContext=context;
        LayoutInflater.from(mContext).inflate(R.layout.collapse_layout, this);
        initView();
    }

    private void initView() {
        mNumberTextView=(TextView)findViewById(R.id.numberTextView);
        mTitleTextView =(TextView)findViewById(R.id.titleTextView);
        mTitleRelativeLayout= (RelativeLayout) findViewById(R.id.titleRelativeLayout);
        mContentRelativeLayout=(RelativeLayout)findViewById(R.id.contentRelativeLayout);
        mArrowImageView =(ImageView)findViewById(R.id.arrowImageView);
        mTitleRelativeLayout.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                rotateArrow();
            }
        });

        collapse(mContentRelativeLayout);
    }

    public void setNumber(String number){
        if(!TextUtils.isEmpty(number)){
            mNumberTextView.setText(number);
        }
    }

    public void setTitle(String title){
        if(!TextUtils.isEmpty(title)){
            mTitleTextView.setText(title);
        }
    }

    public void setContent(int resID){
        View view=LayoutInflater.from(mContext).inflate(resID,null);
        RelativeLayout.LayoutParams layoutParams=
                new RelativeLayout.LayoutParams(LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
        view.setLayoutParams(layoutParams);
        mContentRelativeLayout.addView(view);
    }

    public void rotateArrow() {
        int degree = 0;
        if (mArrowImageView.getTag() == null || mArrowImageView.getTag().equals(true)) {
            mArrowImageView.setTag(false);
            degree = -180;
            expand(mContentRelativeLayout);
        } else {
            degree = 0;
            mArrowImageView.setTag(true);
            collapse(mContentRelativeLayout);
        }
        mArrowImageView.animate().setDuration(duration).rotation(degree);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        parentWidthMeasureSpec=widthMeasureSpec;
        parentHeightMeasureSpec=heightMeasureSpec;
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);
    }

    // 展开
    private void expand(final View view) {
        WindowManager wm = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
        DisplayMetrics outMetrics = new DisplayMetrics();
        wm.getDefaultDisplay().getMetrics(outMetrics);
        view.measure(parentWidthMeasureSpec, parentHeightMeasureSpec);
        final int measuredWidth = view.getMeasuredWidth();
        final int measuredHeight = view.getMeasuredHeight();
        view.setVisibility(View.VISIBLE);

        Animation animation = new Animation() {
            @Override
            protected void applyTransformation(float interpolatedTime, Transformation t) {
                if(interpolatedTime == 1){
                    view.getLayoutParams().height =measuredHeight;
                }else{
                    view.getLayoutParams().height =(int) (measuredHeight * interpolatedTime);
                }
                view.requestLayout();
            }

            @Override
            public boolean willChangeBounds() {
                return true;
            }
        };
        animation.setDuration(duration);
        view.startAnimation(animation);
    }

    // 折叠
    private void collapse(final View view) {
        final int measuredHeight = view.getMeasuredHeight();
        Animation animation = new Animation() {
            @Override
            protected void applyTransformation(float interpolatedTime, Transformation t) {
                if (interpolatedTime == 1) {
                    view.setVisibility(View.GONE);
                } else {
                    view.getLayoutParams().height = measuredHeight - (int) (measuredHeight * interpolatedTime);
                    view.requestLayout();
                }
            }

            @Override
            public boolean willChangeBounds() {
                return true;
            }
        };
        animation.setDuration(duration);
        view.startAnimation(animation);
    }
}

现就该代码中的主要操作做一些分析和介绍。

  1. 控件提供setNumber()方法用于设置最左侧的数字,请参见代码第62-66行。

    比如你有三个女朋友,那么她们的编号分别就是1,2,3

  2. 控件提供setTitle()方法用于设置标题,请参见代码第68-72行

    比如,现女友,前女友,前前女友。

  3. 控件提供setContent()方法用于设置隐藏和显示的内容,请参见代码第74-80行。

    请注意,该方法的参数是一个布局文件的ID。所以,要显示和隐藏的东西只要写在一个布局文件中就行。这样就灵活多了,可以根据实际需求实现不同的布局就行。比如在这个例子中,我在一个布局文件中就只放了一个ImageView,然后将这个布局文件的ID传递给该方法就行。

  4. 实现Title部分Click监听,请参见代码第51-56行

    在监听到Click事件后显示或隐藏content部分

  5. 实现content部分的显示,请参见代码第110-138行

    在这遇到一个难题:

    这个content会占多大的空间呢?

    我猛地这么一问,大家可能有点懵圈。

    如果没有听懂或者回答不上来,我就先举个例子:

    小狗一秒钟跑1米(即小狗的速度为1m/s),请问小狗跑完这段路要多少时间?

    看到这个问题,是不是觉得挺脑残的,是不是有一种想抽我耳光的冲动?

    你他妹的,路程的长短都没有告诉我,我怎么知道小狗要跑多久?!真是日了狗了!

    嗯哼,是的。我们在这里根本不知道这个View(比如此处的content)有多高多宽,我们当然也不知道它要占多大的空间!!那怎么办呢?在这就按照最直接粗暴的方式来——遇到问题,解决问题!找出该View的宽和高!

    前面在分析View的measure阶段时我们知道这些控件的宽和高是由系统测量的,在此之后我们只需要利用getMeasuredWidth()和getMeasuredHeight()就行了。但是这个控件的visibility原本是GONE的,系统在measure阶段根本不会去测量它的宽和高,所以现在需要我们自己去手动测量。代码如下:

    view.measure(parentWidthMeasureSpec, parentHeightMeasureSpec);

  6. 获取到view的宽高后借助于动画实现content的渐次展开,请参见代码第119-137行。

    动画的interpolatedTime在一定时间内(duration)从0变化到1,所以

    measuredHeight * interpolatedTime

    表示了content的高从0到measuredHeight的逐次变化,在这个变化的过程中不断调用

    view.requestLayout();

    刷新界面,这样就达到了预想的效果。

  7. 实现content部分的隐藏,请参见代码第141-161行

    隐藏的过程和之前的逐次显示过程原理是一样的,不再赘述。

  8. 实现箭头的转向,请参见代码第83-95行

    这个比较简单,在此直接用属性动画(ViewPropertyAnimator)让箭头旋转

示例小结:

在该demo中主要采用了手动测量View的方式获取View的大小。



瞅瞅第二个例子,效果如下图:

嗯哼,这个流式布局(FlowLayout)大家可能见过,它常用来做一些标签的显示。比如,我要给我女朋友的照片加上描述,我就可以设置tag为:”贤良淑德”, “女神”, “年轻美貌”, “清纯”, “温柔贤惠”等等。而且在标签的显示过程中,如果这一行没有足够的空间显示下一个标签,那么会先自动换行然后再添加新的标签。

好了,效果已经看到了,我们来瞅瞅它是怎么做的。

package com.stay4it.testflowlayout;

import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;

/**
 * 原创作者:
 * 谷哥的小弟
 *
 * 博客地址:
 * http://blog.csdn.net/lfdfhl
 */
public class MyFlowLayout extends ViewGroup{
    private int  verticalSpacing = 20;
    public MyFlowLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);

        int paddingLeft = getPaddingLeft();
        int paddingRight = getPaddingRight();
        int paddingTop = getPaddingTop();
        int paddingBottom = getPaddingBottom();

        int widthUsed = paddingLeft + paddingRight;
        int heightUsed = paddingTop + paddingBottom;

        int childMaxHeightOfThisLine = 0;
        int childCount = getChildCount();
        for (int i = 0; i < childCount; i++) {
            View child = getChildAt(i);
            if (child.getVisibility() != GONE) {
                int childUsedWidth = 0;
                int childUsedHeight = 0;
                measureChild(child,widthMeasureSpec,heightMeasureSpec);
                childUsedWidth += child.getMeasuredWidth();
                childUsedHeight += child.getMeasuredHeight();

                LayoutParams childLayoutParams = child.getLayoutParams();

                MarginLayoutParams marginLayoutParams = (MarginLayoutParams) childLayoutParams;

                childUsedWidth += marginLayoutParams.leftMargin + marginLayoutParams.rightMargin;
                childUsedHeight += marginLayoutParams.topMargin + marginLayoutParams.bottomMargin;

                if (widthUsed + childUsedWidth < widthSpecSize) {
                    widthUsed += childUsedWidth;
                    if (childUsedHeight > childMaxHeightOfThisLine) {
                        childMaxHeightOfThisLine = childUsedHeight;
                    }
                } else {
                    heightUsed += childMaxHeightOfThisLine + verticalSpacing;
                    widthUsed = paddingLeft + paddingRight + childUsedWidth;
                    childMaxHeightOfThisLine = childUsedHeight;
                }

            }

        }

        heightUsed += childMaxHeightOfThisLine;
        setMeasuredDimension(widthSpecSize, heightUsed);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int paddingLeft = getPaddingLeft();
        int paddingRight = getPaddingRight();
        int paddingTop = getPaddingTop();
        int paddingBottom = getPaddingBottom();

        int childStartLayoutX = paddingLeft;
        int childStartLayoutY = paddingTop;

        int widthUsed = paddingLeft + paddingRight;

        int childMaxHeight = 0;

        int childCount = getChildCount();

        for (int i = 0; i < childCount; i++) {
            View child = getChildAt(i);
            if (child.getVisibility() != GONE) {
                int childNeededWidth, childNeedHeight;
                int left, top, right, bottom;

                int childMeasuredWidth = child.getMeasuredWidth();
                int childMeasuredHeight = child.getMeasuredHeight();

                LayoutParams childLayoutParams = child.getLayoutParams();
                MarginLayoutParams marginLayoutParams = (MarginLayoutParams) childLayoutParams;
                int childLeftMargin = marginLayoutParams.leftMargin;
                int childTopMargin = marginLayoutParams.topMargin;
                int childRightMargin = marginLayoutParams.rightMargin;
                int childBottomMargin = marginLayoutParams.bottomMargin;
                childNeededWidth = childLeftMargin + childRightMargin + childMeasuredWidth;
                childNeedHeight = childTopMargin + childBottomMargin + childMeasuredHeight;

                if (widthUsed + childNeededWidth <= r - l) {
                    if (childNeedHeight > childMaxHeight) {
                        childMaxHeight = childNeedHeight;
                    }
                    left = childStartLayoutX + childLeftMargin;
                    top = childStartLayoutY + childTopMargin;
                    right = left + childMeasuredWidth;
                    bottom = top + childMeasuredHeight;
                    widthUsed += childNeededWidth;
                    childStartLayoutX += childNeededWidth;
                } else {
                    childStartLayoutY += childMaxHeight + verticalSpacing;
                    childStartLayoutX = paddingLeft;
                    widthUsed = paddingLeft + paddingRight;
                    left = childStartLayoutX + childLeftMargin;
                    top = childStartLayoutY + childTopMargin;
                    right = left + childMeasuredWidth;
                    bottom = top + childMeasuredHeight;
                    widthUsed += childNeededWidth;
                    childStartLayoutX += childNeededWidth;
                    childMaxHeight = childNeedHeight;
                }
                child.layout(left, top, right, bottom);
            }
        }
    }

}

现就该代码中的主要操作做一些分析和介绍。

  1. 控件继承自ViewGroup,请参见代码第15行。

    系统自带的布局比如LinearLayout很难满足标签自动换行的功能,所以继承ViewGroup实现自需的设计和逻辑

  2. 重写onMeasure( ),请参见代码第22-71行。

    2.1 获取View宽和高的mode和size,请参见代码第23-26行。

    此处widthSpecSize表示了View的宽,该值在判断是否需要换行时会用到。

    2.2 计算View在水平方向和垂直方向已经占用的大小,请参见代码第33-34行。

    在源码阶段也分析过这些已经占用的大小主要指的是View的padding值。

    2.3 测量每个子View的宽和高,请参见代码第38-67行。

    这一步操作是关键。在这一步中需要测量出来每个子View的大小从而计算出该控件的高度。

    在对代码做具体分析之前,我们先明白几个问题。

    第一点:

    我们常说测量每个子View的宽和高是为了将每个子View的宽累加起来得到父View的宽,将每个子View的高累加起来得到父View的高。

    在此处,控件的宽就是屏幕的宽,所以我们不用去累加每个子View的宽,但是要利用子View的宽判断换行的时机。

    至于控件的高,还是需要将每个子View的高相累加。

    第二点:

    怎么判断需要换行显示新的tag呢?如果:

    这一行已占用的宽度+即将显示的子View的宽度>该行总宽度

    那么就要考虑换行显示该tag

    第三点:

    如果十个人站成一排,那么这个队伍的高度是由谁决定的呢?当然是这排人里个子最高的人决定的。同样的道理,几个tag摆放在同一行,这一行的高度就是由最高的tag的值决定的;然后将每一行的高度相加就是View的总高了。

    嗯哼,明白了这些,我们再看代码就容易得多了。

    第一步:

    利用measureChild( )测量子View,请参见代码第43行。

    第二步:

    计算子View需要占用的宽和高(childUsedWidth和childUsedHeight),请参见代码第51-52行。

    第三步:

    判断和处理是否需要换行,请参见代码第54-63行。

    第四步:

    利用setMeasuredDimension()设置View的宽和高,请参见代码第70行

  3. 重写onLayout( ),请参见代码第75-133行。

    在onMeasure中已经对每个子View进行了测量,在该阶段需要把每个子View摆放在合适的位置。

    所以核心是确定每个子View的left, top, right, bottom。

    在该过程中,同样需要考虑换行的问题,思路也和measure阶段类似,故不再赘述。

嗯哼,完成了该自定义控件的代码,该怎么样使用呢?

mFlowLayout.addView(textView, marginLayoutParams);

通过该方式就可以将一个tag添加到FlowLayout控件中显示。

示例小结:

通过直接继承ViewGroup在其onMeasure( )和onLayout()中分别测量和摆放各子View



PS:如果觉得文章太长,那就直接看视频



好了,这就是和大家一起分享的两个自定义View控件。

who is the next one? ——> TouchEvent

时间: 2024-10-11 23:29:30

自定义View系列教程05--示例分析的相关文章

自定义View系列教程07--详解ViewGroup分发Touch事件

自定义View系列教程01–常用工具介绍 自定义View系列教程02–onMeasure源码详尽分析 自定义View系列教程03–onLayout源码详尽分析 自定义View系列教程04–Draw源码分析及其实践 自定义View系列教程05–示例分析 自定义View系列教程06–详解View的Touch事件处理 自定义View系列教程07–详解ViewGroup分发Touch事件 PS:如果觉得文章太长,那就直接看视频吧 在上一篇中已经分析完了View对于Touch事件的处理,在此基础上分析和理

自定义View系列教程01--常用工具介绍

在自定义View的时候,常常会用到一些Android系统提供的工具.这些工具封装了我们经常会用到的方法,比如拖拽View,计算滑动速度,View的滚动,手势处理等等.如果我们自己去实现这些方法会比较繁琐,而且容易出一些bug.所以,作为自定义View系列教程的开端,先介绍一下这些常用的工具,以便在后续的学习和工作中使用. Configuration ViewConfiguration GestureDetector VelocityTracker Scroller ViewDragHelper

自定义View系列教程04--Draw源码分析及其实践

通过之前的详细分析,我们知道:在measure中测量了View的大小,在layout阶段确定了View的位置. 完成这两步之后就进入到了我们相对熟悉的draw阶段,在该阶段真正地开始对视图进行绘制. 按照之前的惯例,我们来瞅瞅View中draw( )的源码 public void draw(Canvas canvas) { final int privateFlags = mPrivateFlags; final boolean dirtyOpaque = (privateFlags & PFL

自定义View系列教程02--onMeasure源码详尽分析

PS:如果觉得文章太长,那就直接看视频吧 大家知道,自定义View有三个重要的步骤:measure,layout,draw.而measure处于该链条的首端,占据着极其重要的地位:然而对于measure的理解却不是那么容易,许多问题都是一知半解,比如:为什么父View影响到了子View的MeasureSpec的生成?为什么我们自定义一个View在布局时将其宽或者高指定为wrap_content但是其实际是match_parent的效果?子View的specMode和specSize的生成依据又是

自定义View系列教程03--onLayout源码详尽分析

PS:如果觉得文章太长,那就直接看视频吧 在经过measure阶段以后,系统确定了View的测量大小,接下来就进入到layout的过程. 在该过程中会确定视图的显示位置,即子View在其父控件中的位置. 嗯哼,我们直接扒开源码从View的layout( )开始入手. //l, t, r, b分别表示子View相对于父View的左.上.右.下的坐标 public void layout(int l, int t, int r, int b) { if ((mPrivateFlags3 & PFLA

wing带你玩转自定义view系列(2) 简单模仿qq未读消息去除效果

上一篇介绍了贝塞尔曲线的简单应用 仿360内存清理效果 这一篇带来一个  两条贝塞尔曲线的应用 : 仿qq未读消息去除效果. 转载请注明出处:http://blog.csdn.net/wingichoy/article/details/50503630 老规矩,先上效果图: qq的未读消息去除很炫酷,其实就是用了两条贝塞尔曲线,我们按思路来,先来画两个圆,及两条贝塞尔曲线,辅助点为圆心y坐标的一半.我们把下面移动的圆,叫做mMoveCircle. 这样一画,就很简单明了了对不对.只要在拖动的时候

wing带你玩转自定义view系列(1) 仿360内存清理效果

本篇是接自 手把手带你做自定义view系列 宗旨都是一样,带大家一起来研究自定义view的实现,与其不同的是本系列省去了简单的坐标之类的讲解,重点在实现思路,用简洁明了的文章,来与大家一同一步步学习. 转载请注明出处:http://blog.csdn.net/wingichoy/article/details/50500479 上一篇介绍了:神奇的贝塞尔曲线,这篇就来研究其应用. 我自己的学习方法是:学习了贝塞尔曲线之后,去研究他的规律,然后开始联想有没有见过类似的效果,最后自己去研究实现,在没

转载爱哥自定义View系列--Paint详解

上图是paint中的各种set方法 这些属性大多我们都可以见名知意,很好理解,即便如此,哥还是带大家过一遍逐个剖析其用法,其中会不定穿插各种绘图类比如Canvas.Xfermode.ColorFilter等等的用法. set(Paint src) 顾名思义为当前画笔设置一个画笔,说白了就是把另一个画笔的属性设置Copy给我们的画笔,不累赘了 setARGB(int a, int r, int g, int b) 不扯了,别跟我说不懂 setAlpha(int a) 同上 setAntiAlias

转载爱哥自定义View系列--Canvas详解

上面所罗列出来的各种drawXXX方法就是Canvas中定义好的能画什么的方法(drawPaint除外),除了各种基本型比如矩形圆形椭圆直曲线外Canvas也能直接让我们绘制各种图片以及颜色等等,但是Canvas真正屌的我觉得不是它能画些什么,而是对画布的各种活用,上一节最后的一个例子大家已经粗略见识了变换Canvas配合save和restore方法给我们绘制图形带来的极大便利,事实上Canvas的活用远不止此,在讲Canvas之前,我想先给大家说说Canvas中非常屌毛而且很有个性的一个方法: