android view的繪制

1.為什么要自定義View怖喻?

參考鏈接之超精辟的自定義view 繪制

  • UI的奇葩設(shè)計。
  • 多界面的組件復(fù)用岁诉。

2.知識點(diǎn)

2.1 MeasureSpec

MeasureSpec是一個32為的整數(shù)值锚沸,前兩位表示測量的模式Spec
Mode,后30位表示該模式下的規(guī)格大小SpecSize涕癣。

MeasureSpec核心代碼如下:

  public static class MeasureSpec {
        private static final int MODE_SHIFT = 30;
        private static final int MODE_MASK  = 0x3 << MODE_SHIFT;

        /** @hide */
        @IntDef({UNSPECIFIED, EXACTLY, AT_MOST})
        @Retention(RetentionPolicy.SOURCE)
        public @interface MeasureSpecMode {}

        /**
         * Measure specification mode: The parent has not imposed any constraint
         * on the child. It can be whatever size it wants.
         */
        public static final int UNSPECIFIED = 0 << MODE_SHIFT;

        /**
         * Measure specification mode: The parent has determined an exact size
         * for the child. The child is going to be given those bounds regardless
         * of how big it wants to be.
         */
        public static final int EXACTLY     = 1 << MODE_SHIFT;

        /**
         * Measure specification mode: The child can be as large as it wants up
         * to the specified size.
         */
        public static final int AT_MOST     = 2 << MODE_SHIFT;

        /**
         * Creates a measure specification based on the supplied size and mode.
         *
         * The mode must always be one of the following:
         * <ul>
         *  <li>{@link android.view.View.MeasureSpec#UNSPECIFIED}</li>
         *  <li>{@link android.view.View.MeasureSpec#EXACTLY}</li>
         *  <li>{@link android.view.View.MeasureSpec#AT_MOST}</li>
         * </ul>
         *
         * <p><strong>Note:</strong> On API level 17 and lower, makeMeasureSpec's
         * implementation was such that the order of arguments did not matter
         * and overflow in either value could impact the resulting MeasureSpec.
         * {@link android.widget.RelativeLayout} was affected by this bug.
         * Apps targeting API levels greater than 17 will get the fixed, more strict
         * behavior.</p>
         *
         * @param size the size of the measure specification
         * @param mode the mode of the measure specification
         * @return the measure specification based on size and mode
         */
        public static int makeMeasureSpec(@IntRange(from = 0, to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size,
                                          @MeasureSpecMode int mode) {
            if (sUseBrokenMakeMeasureSpec) {
                return size + mode;
            } else {
                return (size & ~MODE_MASK) | (mode & MODE_MASK);
            }
        }

        /**
         * Like {@link #makeMeasureSpec(int, int)}, but any spec with a mode of UNSPECIFIED
         * will automatically get a size of 0. Older apps expect this.
         *
         * @hide internal use only for compatibility with system widgets and older apps
         */
        public static int makeSafeMeasureSpec(int size, int mode) {
            if (sUseZeroUnspecifiedMeasureSpec && mode == UNSPECIFIED) {
                return 0;
            }
            return makeMeasureSpec(size, mode);
        }

        /**
         * Extracts the mode from the supplied measure specification.
         *
         * @param measureSpec the measure specification to extract the mode from
         * @return {@link android.view.View.MeasureSpec#UNSPECIFIED},
         *         {@link android.view.View.MeasureSpec#AT_MOST} or
         *         {@link android.view.View.MeasureSpec#EXACTLY}
         */
        @MeasureSpecMode
        public static int getMode(int measureSpec) {
            //noinspection ResourceType
            return (measureSpec & MODE_MASK);
        }

        /**
         * Extracts the size from the supplied measure specification.
         *
         * @param measureSpec the measure specification to extract the size from
         * @return the size in pixels defined in the supplied measure specification
         */
        public static int getSize(int measureSpec) {
            return (measureSpec & ~MODE_MASK);
        }

        static int adjust(int measureSpec, int delta) {
            final int mode = getMode(measureSpec);
            int size = getSize(measureSpec);
            if (mode == UNSPECIFIED) {
                // No need to adjust size for UNSPECIFIED mode.
                return makeMeasureSpec(size, UNSPECIFIED);
            }
            size += delta;
            if (size < 0) {
                Log.e(VIEW_LOG_TAG, "MeasureSpec.adjust: new size would be negative! (" + size +
                        ") spec: " + toString(measureSpec) + " delta: " + delta);
                size = 0;
            }
            return makeMeasureSpec(size, mode);
        }

        /**
         * Returns a String representation of the specified measure
         * specification.
         *
         * @param measureSpec the measure specification to convert to a String
         * @return a String with the following format: "MeasureSpec: MODE SIZE"
         */
        public static String toString(int measureSpec) {
            int mode = getMode(measureSpec);
            int size = getSize(measureSpec);

            StringBuilder sb = new StringBuilder("MeasureSpec: ");

            if (mode == UNSPECIFIED)
                sb.append("UNSPECIFIED ");
            else if (mode == EXACTLY)
                sb.append("EXACTLY ");
            else if (mode == AT_MOST)
                sb.append("AT_MOST ");
            else
                sb.append(mode).append(" ");

            sb.append(size);
            return sb.toString();
        }
    }
  • 主要方法
方法 用法
makeMeasureSpec 重置MeasureSpec約束
getMode 獲取MeasureSpec的模式specMode
getSize 獲取Measure的模式specSize
  • 三種模式
方法(音標(biāo)) 用法
UNSPECTIFIED(?n'sp?s?fa?d) 未指明尺寸
EXACTLY(?ɡ'z?ktli) 精確的尺寸
AT_MOST 父視圖允許的最大尺寸

2.2 Measure

Measure操作用來計算View的實際大小哗蜈,用于確定當(dāng)前View或ViewGroup的實際寬高。
常用方法如下:

  • onMeasure的源碼
 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    }

由onMeasure的源碼可以看出坠韩,該方法的目的距潘,就是為了確定view的具體寬高;

  • setMeasuredDimension的源碼
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
        boolean optical = isLayoutModeOptical(this);
        if (optical != isLayoutModeOptical(mParent)) {
            Insets insets = getOpticalInsets();
            int opticalWidth  = insets.left + insets.right;
            int opticalHeight = insets.top  + insets.bottom;

            measuredWidth  += optical ? opticalWidth  : -opticalWidth;
            measuredHeight += optical ? opticalHeight : -opticalHeight;
        }
        setMeasuredDimensionRaw(measuredWidth, measuredHeight);
    }

用于設(shè)置View的寬和高只搁,在每一次view尺寸運(yùn)算完成使用音比,可以理解為,onMeasure中最后一個必調(diào)的方法氢惋;

  • setMeasuredDimensionRaw的源碼
private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
        mMeasuredWidth = measuredWidth;
        mMeasuredHeight = measuredHeight;

        mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
    }

設(shè)置每一行的寬高洞翩,很少使用,略焰望;

  • getMeasuredWidth的源碼
public final int getMeasuredWidth() {
        return mMeasuredWidth & MEASURED_SIZE_MASK;
    }

獲取view的寬度骚亿;

  • getLayoutParams()的源碼
 @ViewDebug.ExportedProperty(deepExport = true, prefix = "layout_")
    public ViewGroup.LayoutParams getLayoutParams() {
        return mLayoutParams;
    }

獲取View的Layoutparams(備注:layout_開頭的所有參數(shù)都放在了里面);

  • measureChildren的源碼(ViewGroup)
protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
        final int size = mChildrenCount;
        final View[] children = mChildren;
        for (int i = 0; i < size; ++i) {
            final View child = children[i];
            if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
                measureChild(child, widthMeasureSpec, heightMeasureSpec);
            }
        }
    }

遍歷所有的子View柿估,測量全部View的寬高循未。一般與getMeasuredWidth()或getMeasuredHeight()配合使用;

  • measureChild的源碼(ViewGroup)
protected void measureChild(View child, int parentWidthMeasureSpec,
            int parentHeightMeasureSpec) {
        final LayoutParams lp = child.getLayoutParams();

        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                mPaddingLeft + mPaddingRight, lp.width);
        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                mPaddingTop + mPaddingBottom, lp.height);

        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }

用于測量具體子view的寬和高秫舌;一般與getMeasuredWidth()或getMeasuredHeight()配合使用的妖;

  • getChildAt(i)(ViewGroup)
    獲取子View

  • getChildCount()(ViewGroup)
    獲取子View的數(shù)量

  • 備注:layout_width 三種設(shè)置值的方式,以及對應(yīng)的模式流程

賦值方式 模式流程
wrap_content AT_MOST
match_parent AT_MOST --> EXACTLY
100dp EXACTLY

2.3 Layout(ViewGroup)

Layout的過程是用于確定View在父布局中的位置足陨。由父布局獲取參數(shù)嫂粟,然后將參數(shù)傳遞給子View的layout方法中,將view放在具體的位置墨缘;
在onLayout中沒有太復(fù)雜的邏輯需要處理星虹,相應(yīng)的參數(shù)都可以在onMeasure中獲得。建議使用LayoutParams進(jìn)行傳值镊讼,若子view的值固定宽涌,可以使用makeMeasureSpec進(jìn)行重置約束,通過setMeasuredDimension或onMeasure進(jìn)行設(shè)置子view的約束蝶棋。

實例代碼如下

@Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        for (int i = 0; i < getChildCount(); i++) {
            View child = getChildAt(i);
            child.layout(l, t, r, b);//這里不能直接使用onLayout的參數(shù)卸亮,具體的參數(shù)值可以通過,全局變量或LayoutParams進(jìn)行傳遞玩裙;
        }
    }

2.4 Draw(多用于View)

onDraw操作用于繪制view的具體界面兼贸,可以繪制常見的圖形、文字等吃溅,通過對view的操作溶诞,也可以實現(xiàn)具體的動畫,如MD動畫的實現(xiàn)决侈。
同時螺垢,由于ViewGroup大多為容器,用戶承載view赖歌,很少會使用onDraw枉圃,當(dāng)然,也可以使用Draw繪制背景等俏站。

核心知識點(diǎn)
  • Paint 畫筆
    Paint方法用于在Canvas上繪制內(nèi)容讯蒲,可以設(shè)置Paint的寬度、顏色肄扎、筆觸墨林、以及對圖片進(jìn)行濾鏡處理等。
    詳細(xì)的Paint效果,請查看Android 畫筆Paint

常用的Paint代碼:

 private void initPaint() {
        mPaint = new Paint();
        mPaint.setStyle(Paint.Style.STROKE);//設(shè)置畫筆樣式
        mPaint.setStrokeWidth(10);// 設(shè)置畫筆寬度
        mPaint.setColor(ContextCompat.getColor(getContext(), R.color.colorAccent));//設(shè)置畫筆顏色
        mPaint.setAntiAlias(true);//設(shè)置抗鋸齒
        mPaint.setTextSize(60);//設(shè)置文字尺寸
    }

設(shè)置

筆觸類型 描述
FILL_AND_STROKE 填充內(nèi)部且描邊
STROKE 描邊
FILL 填充內(nèi)部
  • Canvas 畫布

    繪制文字

        /**
         * @param text  文本
         * @param x     水平方向起點(diǎn)
         * @param y     豎直方向的文字底部
         * @param paint 畫筆
         */
        canvas.drawText("畫圓:", 50, 1000, mPaint);

繪制矩形

       /**
         * RectF : 屏幕左上角是坐標(biāo)原點(diǎn),4個參數(shù):left帽衙,top覆糟,right,bottom
         * left:屏幕左邊到矩形左邊的距離
         * top:屏幕頂部到矩形頂邊的距離
         * right:屏幕左邊到矩形右邊的距離
         * bottom:屏幕頂邊到矩形底邊的距離
         *
         * @desc 繪制一個寬50的矩形
         */
        canvas.drawRect(new RectF(50,50,100,100),mPaint);

** 將整個畫布繪制成畫筆的顏色**

       /**
         * 將整個畫布繪制成畫筆的顏色
         */
        canvas.drawPaint(mPaint);

** 繪制一條水平直線**

      /**
         * @param startX X軸的起點(diǎn)
         * @param startY Y軸的起點(diǎn)
         * @param stopX X軸的終點(diǎn)
         * @param stopY Y軸的終點(diǎn)
         * @param paint
         */
        canvas.drawLine(10,10,100,10,mPaint);
   

** 繪制一條水平直線**

      /**
         * @param startX X軸的起點(diǎn)
         * @param startY Y軸的起點(diǎn)
         * @param stopX X軸的終點(diǎn)
         * @param stopY Y軸的終點(diǎn)
         * @param paint
         */
        canvas.drawLine(10,10,100,10,mPaint);
   

繪制圓弧或扇形

        /**
           * oval:圓弧所在的RectF對象阵幸。
           * startAngle:圓弧的起始角度。
           * sweepAngle:圓弧的結(jié)束角度口芍。
           * useCenter:是否顯示半徑連線弃榨,true表示顯示圓弧與圓心的半徑連線菩收,false          表示不顯示。
           * paint:繪制時所使用的畫筆鲸睛。
           */
       //繪制扇形
        RectF oval1 = new RectF(50, 50, 300, 300);
        canvas.drawArc(oval1,90,90,true,mPaint);
        //繪制圓弧
        mPaint.setStyle(Paint.Style.STROKE);
        RectF oval2 = new RectF(150, 150, 600, 600);
        canvas.drawArc(oval2,90,90,false,mPaint);
       

繪制圓角矩形

/**
         * RectF : 屏幕左上角是坐標(biāo)原點(diǎn)娜饵,4個參數(shù):left,top官辈,right箱舞,bottom
         * left:屏幕左邊到矩形左邊的距離
         * top:屏幕頂部到矩形頂邊的距離
         * right:屏幕左邊到矩形右邊的距離
         * bottom:屏幕頂邊到矩形底邊的距離
         * @param rx 圓角X軸半徑
         * @param ry 圓角Y軸半徑
         * @desc 繪制一個寬50的矩形
         */
        canvas.drawRoundRect(new RectF(50,50,100,100),mPaint);

繪制圖片

Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher);
/**
 * @param bitmap The bitmap to be drawn
 * @param left   左上角x坐標(biāo)
 * @param top    左上角y坐標(biāo)
 * @param paint  畫筆
 */
canvas.drawBitmap(bitmap, 360, 1300, mPaint);

繪制點(diǎn)

/**
 * x,y坐標(biāo)
 */
//畫一個點(diǎn)
canvas.drawPoint(500, 1200, p);
//畫多個點(diǎn)
canvas.drawPoints(new float[]{600, 1200, 650, 1250, 700, 1200}, mPaint);

繪制三角形

Path path = new Path();
path.moveTo(500, 750);// 此點(diǎn)為多邊形的起點(diǎn)
path.lineTo(400, 850);
path.lineTo(600, 850);
path.close(); // 使這些點(diǎn)構(gòu)成封閉的多邊形
canvas.drawPath(path, mPaint);

繪制貝塞爾曲線

Path path2 = new Path();
path2.moveTo(500, 1050);//設(shè)置Path的起點(diǎn)
//設(shè)置貝塞爾曲線的控制點(diǎn)坐標(biāo)和終點(diǎn)坐標(biāo)
path2.quadTo(600, 950, 700, 1050);
path2.quadTo(800, 1150, 900, 1050); 
canvas.drawPath(path2, mPaint);

備注:畫線的時候,一定要將畫筆設(shè)置為Paint.Style.STROKE

2.5 自定義xml中的屬性

1. 創(chuàng)建attrs.xml文件

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="FlowLayout">
        <attr name="space" format="dimension"></attr>
    </declare-styleable>
</resources>

declare-styleable 設(shè)置自定義view的名稱拳亿,attr中設(shè)置view中需要使用的具體屬性晴股,以及相應(yīng)屬性的具體格式;

所有屬性的格式如下:

格式 描述 使用
reference 資源ID @drawable/圖片ID
color 顏色值 #ffffff
boolean 布爾值 false or true
dimension 尺寸dp值 100dp or 100dip
float 浮點(diǎn)值 1.0
integer 整型值 100
string 字符串 "str"
fraction 百分?jǐn)?shù) 100%
enum 枚舉類型 使用enum標(biāo)簽設(shè)置
flag 位或運(yùn)算 類事枚舉肺魁,使用flag標(biāo)簽設(shè)置

2. 在View中引用
獲取自定義屬性值

  void init(Context context, AttributeSet attrs) {
        TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.FlowLayout);
        space = ta.getDimensionPixelSize(R.styleable.FlowLayout_space, 0);
        ta.recycle();
    }

init方法應(yīng)該放在以下的構(gòu)造方法中电湘。(備注:默認(rèn)都會調(diào)用以下構(gòu)造方法,無論是否進(jìn)行findViewById)

public FlowLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        Log.e(TAG, "FlowLayout(Context context, AttributeSet attrs)");
        init(context, attrs);
        Log.e(TAG,space+"");
    }

3. 在布局文件中使用

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout 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"
    tools:context="study.zxh.com.viewdemo.MainActivity">

    <study.zxh.com.viewdemo.FlowLayout
        android:id="@+id/fl"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:space="10dp" />

</android.support.constraint.ConstraintLayout>

在跟標(biāo)簽引入

xmlns:app="http://schemas.android.com/apk/res-auto"

在自定義的布局中使用

app:space="10dp"

2.6 LayoutParam

  • 自定義LayoutParams
    @Override
    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
        return p instanceof LayoutParams;
    }

    @Override
    public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
        return new LayoutParams(getContext(), attrs);
    }

    @Override
    protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
        return new LayoutParams(p);
    }

    @Override
    protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
        return new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
    }

    private static class LayoutParams extends ViewGroup.LayoutParams {
        private int left;
        private int top;
        private int right;
        private int bottom;

        public LayoutParams(Context c, AttributeSet attrs) {
            super(c, attrs);
        }

        public LayoutParams(int width, int height) {
            super(width, height);
        }

        public LayoutParams(ViewGroup.LayoutParams source) {
            super(source);
        }

        public LayoutParams(LayoutParams source) {
            super(source);
            this.left = source.left;
            this.top = source.top;
            this.right = source.right;
            this.bottom = source.bottom;
        }
    }
  • 引用步驟
  1. 實現(xiàn)靜態(tài)內(nèi)部類LayoutParams万搔,并繼承自ViewGroup.LayoutParams或其子類胡桨;
  2. 重寫checkLayoutParams用于檢測其類型。
  3. 重寫generateLayoutParams實現(xiàn)自定義LayoutParams的構(gòu)造瞬雹;
  4. 重寫generateDefaultLayoutParams()傳入默認(rèn)的LayoutParams昧谊,一般傳入ViewGroup.LayoutParams。
  5. 使用時酗捌,直接通過view.getLayoutParams()呢诬,獲取子view的LayoutParams使用;

3.自定義View之實現(xiàn)MD按鈕動畫

4.自定義ViewGroup之流式布局

實現(xiàn)的最終效果

實現(xiàn)的最終效果

最終代碼

/**
 * Created by zhangxuehui on 2017/6/16.
 * 實現(xiàn)動態(tài)添加文字標(biāo)簽
 */
public class FlowLayout extends ViewGroup {
    private static final String TAG = "FlowLayout";
    private int space = 0;//文字間的間距胖缤,以及四周的邊距尚镰;


    public FlowLayout(Context context) {
        super(context);
    }

    public FlowLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context, attrs);
        Log.e(TAG, space + "");
    }

    public FlowLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    //初始化自定義的屬性
    void init(Context context, AttributeSet attrs) {
        TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.FlowLayout);
        space = ta.getDimensionPixelSize(R.styleable.FlowLayout_space, 0);
        ta.recycle();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int curWidth = 0;
        int curHeight = 0;
        int maxWidth = 0;
        int maxHeight = 0;
        int pos = 0;//用于判讀最后一行是否有數(shù)據(jù)
        for (int i = 0; i < this.getChildCount(); i++) {
            View child = this.getChildAt(i);
            //測量child的寬高
            measureChild(child, widthMeasureSpec, heightMeasureSpec);
            LayoutParams lp = (LayoutParams) child.getLayoutParams();
            lp.left = curWidth + space;
            lp.top = maxHeight + space;
            lp.right = lp.left + child.getMeasuredWidth();
            lp.bottom = lp.top + child.getMeasuredHeight();
            curWidth = lp.right;
            curHeight = Math.max(child.getMeasuredHeight(), curHeight);
            if (curWidth > widthSize - space) {//翻頁,需要去除右邊距,否則哪廓,最右側(cè)標(biāo)簽可能會貼緊屏幕狗唉;
                //翻頁后,初始化相應(yīng)的參數(shù)
                maxWidth = Math.max(maxWidth, curWidth);
                pos = i;
                curHeight = 0;
                curWidth = 0;
                maxHeight = lp.bottom;
                //重新設(shè)置超出屏幕的view
                lp.left = curWidth + space;
                lp.top = maxHeight + space;
                lp.right = lp.left + child.getMeasuredWidth();
                lp.bottom = lp.top + child.getMeasuredHeight();
                //取得最新的參數(shù)
                curWidth = lp.right;
                curHeight = Math.max(child.getMeasuredHeight(), curHeight);
            }
            Log.e(TAG, lp.toString());
        }
        if (getChildCount() > pos) {
            maxHeight += curHeight + space * 2;
        }
        widthMeasureSpec = MeasureSpec.makeMeasureSpec(maxWidth, MeasureSpec.EXACTLY);//重建約束
        heightMeasureSpec = MeasureSpec.makeMeasureSpec(maxHeight, MeasureSpec.EXACTLY);//重建約束
        setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, MeasureSpec.EXACTLY), resolveSizeAndState(maxHeight, heightMeasureSpec, MeasureSpec.EXACTLY));
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        for (int i = 0; i < this.getChildCount(); i++) {
            View child = this.getChildAt(i);
            LayoutParams lp = (LayoutParams) child.getLayoutParams();
            child.layout(lp.left, lp.top, lp.right, lp.bottom);
        }
    }

    @Override
    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
        return p instanceof LayoutParams;
    }

    @Override
    public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
        return new LayoutParams(getContext(), attrs);
    }

    @Override
    protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
        return new LayoutParams(p);
    }

    @Override
    protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
        return new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
    }

    private static class LayoutParams extends ViewGroup.LayoutParams {
        private int left;
        private int top;
        private int right;
        private int bottom;

        public LayoutParams(Context c, AttributeSet attrs) {
            super(c, attrs);
        }

        public LayoutParams(int width, int height) {
            super(width, height);
        }

        public LayoutParams(ViewGroup.LayoutParams source) {
            super(source);
        }

        public LayoutParams(LayoutParams source) {
            super(source);
            this.left = source.left;
            this.top = source.top;
            this.right = source.right;
            this.bottom = source.bottom;
        }

        public void clear() {
            this.left = 0;
            this.top = 0;
            this.right = 0;
            this.bottom = 0;
        }

        @Override
        public String toString() {
            return "LayoutParams{" +
                    "left=" + left +
                    ", top=" + top +
                    ", right=" + right +
                    ", bottom=" + bottom +
                    '}';
        }
    }
}

5.組合view之微信聊天界面

最終效果
實現(xiàn)思想
首先整個聊天界面通過系統(tǒng)的ListView實現(xiàn)涡真,通過組合控件實現(xiàn)聊天氣泡和輸入框分俯,全部代碼可以劃分為三個組合控件,WeChatMsgList哆料、WeChatMsgInput缸剪、WeChatBubble
最終代碼

  • WeChatMsgList

  • WeChatMsgInput

  • WeChatBubble

參考地址:
Canvas繪圖
Android 畫筆Paint
官方api Canvas

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市东亦,隨后出現(xiàn)的幾起案子杏节,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,839評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件奋渔,死亡現(xiàn)場離奇詭異镊逝,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)卒稳,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評論 2 382
  • 文/潘曉璐 我一進(jìn)店門蹋半,熙熙樓的掌柜王于貴愁眉苦臉地迎上來他巨,“玉大人充坑,你說我怎么就攤上這事∪就唬” “怎么了捻爷?”我有些...
    開封第一講書人閱讀 153,116評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長份企。 經(jīng)常有香客問我也榄,道長,這世上最難降的妖魔是什么司志? 我笑而不...
    開封第一講書人閱讀 55,371評論 1 279
  • 正文 為了忘掉前任甜紫,我火速辦了婚禮,結(jié)果婚禮上骂远,老公的妹妹穿的比我還像新娘囚霸。我一直安慰自己,他們只是感情好激才,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,384評論 5 374
  • 文/花漫 我一把揭開白布拓型。 她就那樣靜靜地躺著,像睡著了一般瘸恼。 火紅的嫁衣襯著肌膚如雪劣挫。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,111評論 1 285
  • 那天东帅,我揣著相機(jī)與錄音压固,去河邊找鬼。 笑死靠闭,一個胖子當(dāng)著我的面吹牛帐我,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播阎毅,決...
    沈念sama閱讀 38,416評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼焚刚,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了扇调?” 一聲冷哼從身側(cè)響起矿咕,我...
    開封第一講書人閱讀 37,053評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后碳柱,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體捡絮,經(jīng)...
    沈念sama閱讀 43,558評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,007評論 2 325
  • 正文 我和宋清朗相戀三年莲镣,在試婚紗的時候發(fā)現(xiàn)自己被綠了福稳。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,117評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡瑞侮,死狀恐怖的圆,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情半火,我是刑警寧澤越妈,帶...
    沈念sama閱讀 33,756評論 4 324
  • 正文 年R本政府宣布,位于F島的核電站钮糖,受9級特大地震影響梅掠,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜店归,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,324評論 3 307
  • 文/蒙蒙 一阎抒、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧消痛,春花似錦且叁、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至稠歉,卻和暖如春掰担,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背怒炸。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評論 1 262
  • 我被黑心中介騙來泰國打工带饱, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人阅羹。 一個月前我還...
    沈念sama閱讀 45,578評論 2 355
  • 正文 我出身青樓勺疼,卻偏偏與公主長得像,于是被迫代替她去往敵國和親捏鱼。 傳聞我的和親對象是個殘疾皇子执庐,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,877評論 2 345

推薦閱讀更多精彩內(nèi)容

  • View的繪制和事件處理是兩個重要的主題,上一篇《圖解 Android事件分發(fā)機(jī)制》已經(jīng)把事件的分發(fā)機(jī)制講得比較詳...
    Kelin閱讀 119,174評論 100 845
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,517評論 25 707
  • Android的UI管理系統(tǒng)層級關(guān)系 PhoneWindow是Adroid系統(tǒng)中最基本的窗口系統(tǒng)导梆,每個Activi...
    凱玲之戀閱讀 1,871評論 0 2
  • 直接了當(dāng)?shù)膩碚f轨淌,Android系統(tǒng)中View的繪制需要經(jīng)歷三個主要過程: onMeasure()迂烁、onLayou...
    PANWCS閱讀 516評論 0 0
  • 今天上午通過微博大V龔老師的微博了解到了朋友圈推出的搜索新功能“那年今日”,可以在微信朋友圈搜索到往年今日的朋友圈...
    18c5643b7880閱讀 1,459評論 5 2