您好,登錄后才能下訂單哦!
這篇文章給大家介紹Android中如何自定義View,內容非常詳細,感興趣的小伙伴們可以參考借鑒,希望對大家能有所幫助。
前言
View,有很多的名稱。不論是你熟知的布局,還是控件,他們全部都繼承自View。
文內部分圖片轉載自Carson_Ho大佬的文章
思維導圖
工作流程
measure
其實通過layout中的第二張圖我們已經知道了控件大小的計算了。
height = bottom - top
width = right - left
對于ViewGroup而言,就是對容器內子控件的遍歷和計算了。
因為直接繼承自View的控件使用wrap_cotent和match_parent是顯示出來的效果是相同的。需要我們使用MeasureSpec中的getMode()方法來對當前的模式進行區分和比較。
模式 | 狀態 |
---|---|
UNSPECIFIED | 未指定模式,View想多大就多大,父容器不做限制,一般用于系統內部的測量 |
AT_MOST | 最大模式,對應wrap_content,View的大小不大于SpecSize的值 |
EXACTLY | 精確模式,對應match_parent,View的大小為SpecSize的值 |
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); //用于獲取設定的模式 int widthMode = MeasureSpec.getMode(widthMeasureSpec); int heightMode = MeasureSpec.getMode(heightMeasureSpec); // 用于獲取設定的長度 int widthSize = MeasureSpec.getSize(widthMeasureSpec); int heightSize = MeasureSpec.getSize(heightMeasureSpec); // 類似這樣的判斷,后面不過多復述 // 用于判斷是不是wrap_content // 如果不進行處理,效果會是match_parent if(widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST){ setMeasuredDimension(20, 20); } }
layout
在確定位置時,我們有一個非常需要主要的地方—— 坐標系。
Android系統的坐標系和平時畫的坐標系并不相同。
所以相對應的,我們的位置計算方法自然和我們原來的正好是相反的。
4個頂點的位置分別由4個值決定:
top:子View上邊界到所在容器上邊界的距離。
left:子View左邊界到所在容器左邊界的距離。
bottom:子View下邊界到所在容器上邊界的距離。
right:子View右邊界到所在容器左邊界的距離。
所有的計算都是相對于所在容器才能夠開始的。
draw
一共有6個步驟:
如果需要,則繪制背景 --drawBackground(canvas);
保存當前canvas層 --saveCount = canvas.getSaveCount();
繪制View的內容 --if (!dirtyOpaque) onDraw(canvas);
繪制子View --dispatchDraw(canvas);
如果需要,則繪制View的褪色邊緣,類似于陰影效果 --canvas.restoreToCount(saveCount);
繪制裝飾,比如滾動條 --onDrawForeground(canvas);
關于開發者需要重寫的方法一般是第三步繪制View的內容對應的onDraw()。
private Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); int width = getWidth(); int height = getHeight(); // 在畫布上進行類似這樣的操作 canvas.drawLine(0, height/2, width,height/2, paint); }
入門自定義View
在日常項目的布局文件中我們經常會使用到xmlns:app="http://schemas.android.com/apk/res-auto"這樣標簽,其實他就是用來引入我們自定義的標簽使用的。
1.在res/values目錄下創建attrs
<?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="DefaultView"> <attr name="color" format="color"/> </declare-styleable> </resources>
2.在DefaultView(Context context, @Nullable AttributeSet attrs)中獲取。以下是整個完整代碼。
/** * author: ClericYi * time: 2020-01-30 */ public class DefaultView extends View { private Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); private int mColor = Color.RED; public DefaultView(Context context, @Nullable AttributeSet attrs) { super(context, attrs); initAttrs(context, attrs); initDraw(); } private void initAttrs(Context context, @Nullable AttributeSet attrs) { TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.DefaultView); // 從styleable中獲取的名字是系統會生成的,一般是 類名_name 的形式 mColor = array.getColor(R.styleable.DefaultView_color, Color.GREEN); // 獲取完資源后即使回收 array.recycle(); } private void initDraw() { paint.setColor(mColor); paint.setStrokeWidth(3f); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); int width = getWidth(); int height = getHeight(); canvas.drawLine(0, height/2, width,height/2, paint); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int widthMode = MeasureSpec.getMode(widthMeasureSpec); int heightMode = MeasureSpec.getMode(heightMeasureSpec); int widthSize = MeasureSpec.getSize(widthMeasureSpec); int heightSize = MeasureSpec.getSize(heightMeasureSpec); if(widthMode == MeasureSpec.AT_MOST){ setMeasuredDimension(20, 20); } } }
基礎的性能優化
首先的話我們先了解如何去知道一個View是否被過度繪制了?
其實在我們手機中的開發模式已經存在這個選項了。
下方給出繪制的次數對應圖
那如何做到性能優化呢?
在這個問題之前,需要了解什么是過度繪制,你可以理解為同一位置的控件不斷的疊加而產生的無用數據,那我們就來說說集中解決方案吧。
方案1:減少嵌套層數。
因為只是一個案例,想說的意思,如果多個LinearLayout嵌套實現的效果,如果能被一個ConstraintLayout直接實現,那么就用后者替代,因為不會這樣在同一個區域重復出現。
方案2:去除默認的背景
這個解決方案其實針對的背景會被自動繪制的問題,如果我們把這個層次消去,從繪制角度老說也是一種提升了。正如圖示一般直接減少了一層的繪制。
在代碼中的具體表現,通過對style.xml中的Theme進行修改:
<item name="android:windowBackground">@null</item>
總結
關于Android中如何自定義View就分享到這里了,希望以上內容可以對大家有一定的幫助,可以學到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。