国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 學院 > 開發設計 > 正文

自定義View

2019-11-09 15:16:31
字體:
來源:轉載
供稿:網友

自定義ViewGroup

當自定義ViewGroup時,主要需要重寫onMeasure計算高度和寬度,重寫onLayout為每個子View設置位置。 在onMeasure中設置的寬度和高度時,需要注意的是這個高度和寬度應該是包括padding的;在onLayout中為每個子View設置的位置應該是不包含每個子View的左右上下margin的。 另外需要注意的是,如果需要提供LayoutParams,需要重寫generateLayoutParams(AttributeSet attrs)方法返回一個LayoutParams,這個參數就是其子View調用getLayoutParams返回的LayoutParams,重寫這個這個方法時,如果所有的布局均是在xml中完成的,那么不會出現問題,而如果一旦調用addView方法,則會拋出異常,如果需要支持addView方法,那么需要重寫generateDefaultLayoutParams()方法返回一個默認的LayoutParams。 下面是一個標簽流式布局的示例:

/** * 標簽布局 * Created by Xingfeng on 2016-10-20. */public class TagLayout extends ViewGroup { public TagLayout(Context context) { super(context); } public TagLayout(Context context, AttributeSet attrs) { super(context, attrs); } public TagLayout(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @TargetApi(Build.VERSION_CODES.LOLLipOP) public TagLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); } /** * 計算高度和寬度 * @param widthMeasureSpec * @param heightMeasureSpec */ @Override PRotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int widthSize = MeasureSpec.getSize(widthMeasureSpec); int widthMode = MeasureSpec.getMode(widthMeasureSpec); int heightSize = MeasureSpec.getSize(heightMeasureSpec); int heightMode = MeasureSpec.getMode(heightMeasureSpec); int count = getChildCount(); int lineHeight = 0; int lineWidth = 0; int width = 0; int height = 0; //遍歷子View for (int i = 0; i < count; i++) { View child = getChildAt(i); //測量子View measureChild(child, widthMeasureSpec, heightMeasureSpec); MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); //得到子View占據的寬度和高度,包括marigin int w = child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin; int h = child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin; //如果一行寬度超過了TagLayout的寬度,不包括左右padding if (lineWidth + w > widthSize - getPaddingLeft() - getPaddingRight()) { //高度加上上一行的高度 height += lineHeight; //高度取所有行中最寬的 width = Math.max(width, lineWidth); lineHeight = h; lineWidth = w; } //不需要換行 else { //每一行的高度以最大的高度為準 lineHeight = Math.max(lineHeight, h); lineWidth += w; } //如果是最后一個View,因為可能沒有轉行,所以要對寬度做個判斷,有可能最后一行就是最寬的,總的高度需要加上最后一行的高度 if (i == count - 1) { width = Math.max(width, lineWidth); height += lineHeight; } } //確定寬高,如果是確定的,則使用約束的,否則使用計算得到值 int w = widthMode == MeasureSpec.EXACTLY ? widthSize : width + getPaddingLeft() + getPaddingRight(); int h = heightMode == MeasureSpec.EXACTLY ? heightSize : height + getPaddingTop() + getPaddingBottom(); //千萬記得調用該方法 setMeasuredDimension(w, h); } /** * 對子View進行位置安放 * @param changed * @param l * @param t * @param r * @param b */ @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { //安放的起始位置是左上角,去除左和上padding部分 int left = getPaddingLeft(); int top = getPaddingTop(); int lineWidth = 0; int lineHeight = 0; int width = getWidth(); int count = getChildCount(); for (int i = 0; i < count; i++) { View child = getChildAt(i); MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); int vWidth = child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin; int vHeight = child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin; //換行 if (vWidth + lineWidth > width - getPaddingRight() - getPaddingLeft()) { //計算下一行的高度 top += lineHeight; lineWidth = vWidth; //下一行左邊的開始 left = getPaddingLeft(); } else { //每一行高度取最大的一個 lineHeight = Math.max(lineHeight, vHeight); //行寬 lineWidth += vWidth; } child.layout(left + lp.leftMargin, top + lp.topMargin, left + +lp.leftMargin + child.getMeasuredWidth(), top + lp.topMargin + child.getMeasuredHeight()); //左起始 left += vWidth; } } @Override public LayoutParams generateLayoutParams(AttributeSet attrs) { return new MarginLayoutParams(getContext(), attrs); } /** * 用于支持addView方法 * @return */ @Override protected LayoutParams generateDefaultLayoutParams() { return new MarginLayoutParams(200,700); }}

自定義View

自定義View一般分為三種: 1. 組合View,利用基本View進行組合,得到一個新的View 2. 繼承現有View,增加新功能 3. 繼承View,自定義內容的繪制以及事件的處理

下面以三個例子分為介紹這三種情況。

1. 組合View

組合View一個典型的例子,就是應用的頂部類似ActionBar的一個例子,比如說左邊一個返回按鈕,中間是標題,最右邊又是一個按鈕。下面就以這個為例,介紹一下組合View的步驟:

編寫xml布局

XML布局如下:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="48dp" android:orientation="horizontal"> <Button android:id="@+id/left_btn" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:text="返回" /> <TextView android:id="@+id/middle_title" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="4" android:background="#DD0000" android:gravity="center" android:text="組合View" /> <Button android:id="@+id/right_btn" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:text="更多" /></LinearLayout>

編寫TabBar類

雖然是組合View,但依然要依附于某個View,不然就不能在XML中使用了,這里我們使TabBar繼承自FrameLayout,然后將上面的XML文件加載到FrameLayout里,這樣就可以在XML中使用了,而XML中的每個控件也可以找到了,如下:

public class TabBar extends FrameLayout { private Button leftBtn, rightBtn; private TextView titleTv; public TabBar(Context context, AttributeSet attrs) { super(context, attrs); //使上面的XML文件加載為FrameLayout的子View LayoutInflater.from(context).inflate(R.layout.tabbar_layout, this); leftBtn = (Button) findViewById(R.id.left_btn); titleTv = (TextView) findViewById(R.id.middle_title); rightBtn = (Button) findViewById(R.id.right_btn); }}

當然,TabBar還可以寫出一些接口供用戶設置按鈕屬性等等,這兒就不介紹了,想了解的朋友可以到最下面的源碼查看。 效果如下: 組合View效果

2. 繼承已有View

下面以一個CircleView為例介紹下繼承已有View,CircleView繼承自TextView,主要就是用于沒有設置背景時,在文本后繪制一個圓形的背景。效果如下圖: 繼承已有View效果 代碼如下,主要就是重寫onDraw方法,在調用TextView的onDraw方法之前首先繪制一個盡可能大的圓。

@Override protected void onDraw(Canvas canvas) { Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); paint.setColor(Color.BLUE); int width = getWidth(); int height = getHeight(); int radius = width > height ? height / 2 : width / 2; canvas.drawCircle(width / 2, height / 2, radius, paint); super.onDraw(canvas); }

3. 繼承View

當組合View和繼承已有View不能滿足我們的需求時,那么需要繼承View,一步一步實現自定義View。主要有如下幾步: 1. 編寫attrs.xml文件定義屬性,這些屬性是可以直接在XML中指定了,就像layout_width等等 2. 繼承View,在構造方法中獲取到XML文件中的各屬性以及賦值 3. 重寫onMeasure方法處理高寬為wrap_content的情況 4. 重寫onDraw繪制內容

下面以一個ArcProgress(弧形進度條)為例,模仿魅族5.0.1的垃圾清理進度條。

1. 編寫attrs文件

attrs.xml文件位于res/value目錄下,主要需要定義中間文字尺寸、顏色、當前進度值、弧形的寬度、顏色、底部標題的文字、顏色和尺寸。

<declare-styleable name="ArcProgress"> <attr name="progress_text_size" format="dimension|reference"></attr> <attr name="progress_text_color" format="color|reference"></attr> <attr name="arc_progress" format="integer|reference"></attr> <attr name="arc_stroke_width" format="dimension|reference"></attr> <attr name="arc_color" format="color|reference"></attr> <attr name="arc_bottom_text" format="string|reference"></attr> <attr name="arc_bottom_text_color" format="color|reference"></attr> <attr name="arc_bottom_text_size" format="dimension|reference"></attr> </declare-styleable>

2. 繼承View

在View的構造方法中使用TypedArray進行獲取屬性值并賦值,如下:

public ArcProgress(Context context) { this(context, null); } public ArcProgress(Context context, AttributeSet attrs) { this(context, attrs, 0); } public ArcProgress(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); initDefaultValues(context); initAttrs(context, attrs); initPaints(); } @TargetApi(Build.VERSION_CODES.LOLLIPOP) public ArcProgress(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); initDefaultValues(context); initAttrs(context,attrs); initPaints(); } private void initDefaultValues(Context context){ DEFAULT_COLOR= Color.parseColor(DEFAULT_COLOR_STR); DEFAULT_STROKE_WIDTH= Utils.dp2px(context.getResources(), DEFAULT_STROKE_WIDTH_F); DEFAULT_TEXT_COLOR=Color.parseColor(DEAULT_TEXT_COLOR_STR); DEFAULT_TEXT_SIZE=Utils.sp2px(context.getResources(), DEFAULT_TEXT_SIZE_F); DEFAULT_INSIDE_STORKE_WIDTH= Utils.dp2px(getResources(),DEFAULT_INSIDE_STORKE_WIDTH_F); } private void initAttrs(Context context,AttributeSet attrs){ TypedArray ta=context.obtainStyledAttributes(attrs,R.styleable.ArcProgress); arcColor=ta.getColor(R.styleable.ArcProgress_arc_color,DEFAULT_COLOR); strokeWidth=ta.getDimension(R.styleable.ArcProgress_arc_stroke_width, DEFAULT_STROKE_WIDTH); progressTextColor=ta.getColor(R.styleable.ArcProgress_progress_text_color, DEFAULT_COLOR); progressTextSize=ta.getDimension(R.styleable.ArcProgress_progress_text_size,DEFAULT_TEXT_SIZE); bottomText=ta.getString(R.styleable.ArcProgress_arc_bottom_text); bottomTextColor =ta.getColor(R.styleable.ArcProgress_arc_bottom_text_color, DEFAULT_TEXT_COLOR); bottomTextSize=ta.getDimension(R.styleable.ArcProgress_arc_bottom_text_size, DEFAULT_TEXT_SIZE); progress=ta.getInt(R.styleable.ArcProgress_arc_progress, 0); ta.recycle(); } private void initPaints(){ arcPaint=new Paint(Paint.ANTI_ALIAS_FLAG); arcPaint.setColor(arcColor); arcPaint.setStyle(Paint.Style.STROKE); textPaint=new Paint(Paint.ANTI_ALIAS_FLAG); }}

在構造方法內完成初始化操作,包括屬性的默認值,獲取屬性值以及Paint的設置。

3. 重寫onMeasure方法

當需要處理wrap_content屬性時,需要重寫該方法。

@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int widthMode=MeasureSpec.getMode(widthMeasureSpec); int heightMode=MeasureSpec.getMode(heightMeasureSpec); int widthSize=MeasureSpec.getSize(widthMeasureSpec); int heightSize=MeasureSpec.getSize(heightMeasureSpec); //如果寬度是wrap_content,使用建議的最小寬度 if(widthMode==MeasureSpec.AT_MOST){ widthSize=getSuggestedMinimumWidth(); } //如果高度是wrap_content,使用建議的最小高度 if(heightMode==MeasureSpec.AT_MOST){ heightSize=getSuggestedMinimumHeight(); } setMeasuredDimension(widthSize, heightSize); } @Override protected int getSuggestedMinimumHeight() { return (int) Utils.dp2px(getResources(),MINIMUM_HEIGHT); } @Override protected int getSuggestedMinimumWidth() { return (int)Utils.dp2px(getResources(),MINIMUM_WIDTH); }

從上面的代碼可以看到,當使用wrap_content時,使用建議的最小值,重寫了建議值,默認值是80dp。

4. 重寫onDraw完成內容繪制

@Override protected void onDraw(Canvas canvas) { int width=getWidth(); int height=getHeight(); int size=Math.min(width, height); //考慮 width>size 和 height>size 的情況 float left=size*0.2f+(width-size); float top=size*0.2f+(height-size); float right=size*0.8f-(width-size); float bottom=size*0.8f-(height-size); arcPaint.setStrokeWidth(DEFAULT_INSIDE_STORKE_WIDTH); //首先畫外弧 canvas.drawArc(new RectF(left, top, right, bottom), START_ANGLE, TOTAL_ANGLE, false, arcPaint); left-=DEFAULT_INSIDE_STORKE_WIDTH; top-=DEFAULT_INSIDE_STORKE_WIDTH; right+=DEFAULT_INSIDE_STORKE_WIDTH; bottom+=DEFAULT_INSIDE_STORKE_WIDTH; arcPaint.setStrokeWidth(DEFAULT_STROKE_WIDTH); //再畫內弧 canvas.drawArc(new RectF(left, top, right, bottom), START_ANGLE, progress * 3, false, arcPaint); //畫進度文字 textPaint.setTextSize(progressTextSize); textPaint.setColor(progressTextColor); float textHeight=textPaint.ascent()+textPaint.descent(); String progressText=progress+"%"; float textWidth=textPaint.measureText(progressText); canvas.drawText(progressText,(width-textWidth)/2,(height-textHeight)/2,textPaint); //畫底部文字 if(!TextUtils.isEmpty(bottomText)){ textPaint.setTextSize(bottomTextSize); textPaint.setColor(bottomTextColor); textHeight=textPaint.ascent()+textPaint.descent(); textWidth=textPaint.measureText(bottomText); canvas.drawText(bottomText,(width-textWidth)/2,height/2+size/2*0.8f+textHeight/2,textPaint); } }

在完成了整個編寫工作后,還可以給該View提供一些額外的設置方法。首先看一下效果: ArcProgress效果圖 上圖模仿了一個下載動作,進度從0變成了100。 該部分關于ArcProgress的代碼可以查看ArcProgress源碼 至此,三種自定義View的方式就都介紹了,具體應該使用哪種方式視情況而定。

總結

本篇博客主要介紹了自定義View。首先介紹了自定義ViewGroup,需要注意的是onMeasure方法和onLayout方法,以及需要返回LayoutParams對象;自定義View時,有三種選擇,可以組合View,可以繼承已有View,也可以直接繼承View,自己編寫邏輯。

關于本篇博客中的所有代碼均在我的Github地址。


發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 大余县| 常德市| 宝兴县| 丹阳市| 汉阴县| 子洲县| 略阳县| 长春市| 阳曲县| 金门县| 平塘县| 磐安县| 西安市| 东城区| 浦江县| 辽源市| 沧州市| 佛学| 屏边| 攀枝花市| 大姚县| 石门县| 博客| 沙洋县| 临西县| 泸州市| 定边县| 施甸县| 苗栗市| 莲花县| 应城市| 福安市| 通许县| 郁南县| 固安县| 巫溪县| 上虞市| 白玉县| 兴隆县| 洪洞县| 托克托县|