1.為什么要自定義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)
獲取子ViewgetChildCount()(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;
}
}
- 引用步驟
- 實現(xiàn)靜態(tài)內(nèi)部類LayoutParams万搔,并繼承自ViewGroup.LayoutParams或其子類胡桨;
- 重寫checkLayoutParams用于檢測其類型。
- 重寫generateLayoutParams實現(xiàn)自定義LayoutParams的構(gòu)造瞬雹;
- 重寫generateDefaultLayoutParams()傳入默認(rèn)的LayoutParams昧谊,一般傳入ViewGroup.LayoutParams。
- 使用時酗捌,直接通過view.getLayoutParams()呢诬,獲取子view的LayoutParams使用;
3.自定義View之實現(xiàn)MD按鈕動畫
4.自定義ViewGroup之流式布局
實現(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