日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

android面试自定义view,资深面试官:自定义View的实现方式,你知道几种?

發布時間:2024/9/19 编程问答 36 豆豆
生活随笔 收集整理的這篇文章主要介紹了 android面试自定义view,资深面试官:自定义View的实现方式,你知道几种? 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

前提

為什么要自定義View?

怎么自定義View?

當 Android SDK 中提供的系統 UI 控件無法滿足業務需求時,我們就需要考慮自己實現 UI 控件。而且自定義View在面試時是很經典的一道Android面試題,對其的理解程度決定面試官看你的高度。

PS:關于我

本人是一個擁有6年開發經驗的帥氣Android工程師,記得看完點贊,養成習慣,微信搜一搜「 程序猿養成中心 」關注這個喜歡寫干貨的程序員。

另外耗時兩年整理收集的Android一線大廠面試完整考點PDF出爐,資料【完整版】已更新在我的【Github】,有面試需要、或者想梳理自己的Android知識體系的朋友們可以去參考參考,如果對你有幫助,可以點個Star哦!

自定義View的方式

繼承系統提供的成熟控件(比如 LinearLayout、RelativeLayout、ImageView 等);

直接繼承自系統 View 或者 ViewGroup,并自繪顯示內容。

建議:盡量直接使用系統提供的UI控件、或者方式1實現需求效果,因為google提供的UI控件等都已經做了非常完整的邊界、特殊case處理。自定義的View可能由于考慮不周全在適配,某些特殊case下出現問題等。

方式1: 繼承系統UI控件

舉例:繼承RelativeLayout實現一個titleBar

1 添加布局

注意:這里使用merge標簽,因為后面我們extends RelativeLayout,不需要再套一層

xmlns:tool="http://schemas.android.com/tools"

android:layout_width="match_parent"

android:layout_height="wrap_content">

android:id="@+id/left_button"

android:layout_width="40dp"

android:layout_height="56dp"

android:layout_marginStart="6dp"

android:paddingStart="5dp"

android:paddingTop="13dp"

android:paddingEnd="5dp"

android:paddingBottom="13dp"/>

android:id="@+id/title_view"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:layout_centerVertical="true"

android:layout_marginStart="20dp"

android:layout_marginEnd="20dp"

android:layout_toStartOf="@+id/right_button"

android:layout_toEndOf="@id/left_button"

android:ellipsize="end"

android:gravity="center"

android:maxLines="1"

android:textColor="@color/color_ffffff"

android:textSize="18sp"

tool:text="This is tools text"/>

android:id="@+id/right_button"

android:layout_width="40dp"

android:layout_height="40dp"

android:layout_alignParentEnd="true"

android:layout_marginTop="7dp"

android:layout_marginEnd="6dp"

android:padding="5dp"/>

2 添加自定義屬性

在valules的attrs.xml中定義如下自定義屬性

name : 是屬性名稱

format : 代表屬性的格式

使用自定義屬性的時候需要添加命名空間如:xmlns:app(可以自己定義)

3 TitleBar代碼實現

public class TitleBarLayout extends RelativeLayout {

public TitleBarLayout(Context context) {

this(context, null);

}

public TitleBarLayout(Context context, AttributeSet attrs) {

this(context, attrs, 0);

}

public TitleBarLayout(Context context, AttributeSet attrs, int defStyleAttr) {

super(context, attrs, defStyleAttr);

// 引入步驟1中添加的布局

ViewUtils.inflate(this, R.layout.layout_title_bar, true);

// 獲取自定義屬性

TypedArray typedArray = context.obtainStyledAttributes(attrs,R.styleable.TitleBar);

Drawable leftDrawable = typedArray.getDrawable(R.styleable.TitleBar_title_left_icon);

Drawable rightDrawable = typedArray.getDrawable(R.styleable.TitleBar_title_right_icon);

CharSequence titleText = typedArray.getString(R.styleable.TitleBar_title_bar_title_text);

int titleTextColor = typedArray.getColor(R.styleable.TitleBar_title_bar_text_color, Color.BLACK);

// 如果不調用recycle,As會有提示

typedArray.recycle();

ImageView leftButton = findViewById(R.id.left_button);

ImageView rightButton = findViewById(R.id.right_button);

TextView titleTextView = findViewById(R.id.title_view);

if (leftDrawable != null) {

leftButton.setImageDrawable(leftDrawable);

}

if (rightDrawable != null) {

rightButton.setImageDrawable(rightDrawable);

}

titleTextView.setText(titleText);

titleTextView.setTextColor(titleTextColor);

}

}

方式2: 繼承View / ViewGroup

自定義View的時候,一般是3步走:

1 重寫onMeasure

測量子View及View本身的大小

@Override

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

super.onMeasure(widthMeasureSpec, heightMeasureSpec);

}

為什么我們需要重寫onMeasure?

如果我們直接在 XML 布局文件中定義好 View 的寬高,然后讓自定義 View 在此寬高的區域內顯示即可,那么就不需要重寫onMeasure了。

但是Android 系統提供了 wrap_content 和 match_parent 屬性來規范控件的顯示規則。它們分別代表自適應大小和填充父視圖的大小,但是這兩個屬性并沒有指定具體的大小,因此我們需要在 onMeasure 方法中過濾出這兩種情況,真正的測量出自定義 View 應該顯示的寬高大小.

MesureSpec

MesureSpec 定義 :

測量規格,View根據該規格從而決定自己的大小。

MeasureSpec是由一個32位 int 值來表示的。其中該 int 值對應的二進制的高2位代表SpecMode,低30位代表SpecSize

測量模式

EXACTLY:表示在 XML 布局文件中寬高使用 match_parent 或者固定大小的寬高;

AT_MOST:表示在 XML 布局文件中寬高使用 wrap_content;

UNSPECIFIED:父容器沒有對當前 View 有任何限制,當前 View 可以取任意尺寸,比如 ListView 中的 item。

自定義FlowLayout onMeasure方法說明(看注釋 !看助手!看注釋!)

@Override

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

super.onMeasure(widthMeasureSpec, heightMeasureSpec);

// 寬度測量模式

int widthMode = MeasureSpec.getMode(widthMeasureSpec);

// 高度測量模式

int heightMode = MeasureSpec.getMode(heightMeasureSpec);

// 測量寬度

int width = MeasureSpec.getSize(widthMeasureSpec);

// 測量高度

int height = MeasureSpec.getSize(heightMeasureSpec);

// 每一行的寬度(FlowLayout當標簽長度超過一行后會換行)

int lineWidth = 0;

// 最終測量的width

int resultWidth = 0;

// 最終測量的height

int resultHeight = 0;

// 換行次數

int lineCount = 1;

// 遍歷所有子View,拿到每一行的最大寬度 和 換行次數

for (int i = 0; i < getChildCount(); i++) {

View childAt = getChildAt(i);

measureChild(childAt, widthMeasureSpec, heightMeasureSpec);

MarginLayoutParams layoutParams = (MarginLayoutParams) childAt.getLayoutParams();

lineWidth += childAt.getMeasuredWidth() + layoutParams.leftMargin + layoutParams.rightMargin;

// 換行

if (lineWidth > width) {

resultWidth = Math.max(lineWidth - childAt.getMeasuredWidth(), resultWidth);

lineWidth = 0;

lineCount++;

}

}

View lastChild = getChildAt(getChildCount() - 1);

MarginLayoutParams marginParams = (MarginLayoutParams) lastChild.getLayoutParams();

// 根據換行次數 + 2(第一行和最后一行) * 高度,算出最終的高度

resultHeight += (lastChild.getMeasuredHeight() + marginParams.topMargin + marginParams.bottomMargin) * (lineCount + 2);

resultWidth += getPaddingLeft() + getPaddingRight();

resultHeight += getPaddingTop() + getPaddingBottom();

// 重寫onMeasure必須調用這個方法來保存測量的寬、高

setMeasuredDimension(widthMode == MeasureSpec.AT_MOST ? resultWidth : width,

heightMode == MeasureSpec.AT_MOST ? resultHeight : height);

}

2 重寫onDraw

繪制自身內容

@Override

protected void onDraw(Canvas canvas) {

super.onDraw(canvas);

}

onDraw 方法接收一個 Canvas 類型的參數。Canvas 可以理解為一個畫布,在這塊畫布上可以繪制各種類型的 UI 元素。

Canvas 中每一個繪制操作都需要傳入一個 Paint 對象。Paint 就相當于一個畫筆,我們可以通過設置畫筆的各種屬性。

如果不想看Canvas、Paint枯燥的文檔,可以搜索Hencoder,看下hencoder大佬的自定義教程,有趣生動。

3、 重寫onLayout

擺放子View位置(繼承ViewGroup必須要重寫)

自定義FlowLayout onLayout方法說明(看注釋 !看注釋!看注釋!)

@Override

protected void onLayout(boolean changed, int l, int t, int r, int b) {

// 獲取FlowLayout 寬度

int parentWidth = getMeasuredWidth();

// 子View擺放的位置

int left, top, right, bottom;

// 一行的寬度

int lineWidth = 0;

// 一行的高度

int lineHeight = 0;

// 遍歷子View 計算它們應該擺放的位置

for (int i = 0; i < getChildCount(); i++) {

View childAt = getChildAt(i);

int childWidth = childAt.getMeasuredWidth();

int childHeight = childAt.getMeasuredHeight();

MarginLayoutParams layoutParams = (MarginLayoutParams) childAt.getLayoutParams();

left = lineWidth + layoutParams.leftMargin;

right = left + childWidth + layoutParams.rightMargin;

top = lineHeight + layoutParams.topMargin;

bottom = top + childHeight + layoutParams.bottomMargin;

if (right > parentWidth) {

lineWidth = 0;

lineHeight += childHeight + layoutParams.topMargin + layoutParams.bottomMargin;

left = lineWidth + layoutParams.leftMargin;

right = left + childWidth + layoutParams.rightMargin;

top = lineHeight + layoutParams.topMargin;

bottom = top + childHeight + layoutParams.bottomMargin;

}

childAt.layout(left, top, right, bottom);

lineWidth += childWidth + layoutParams.rightMargin + layoutParams.leftMargin;

}

}

效果圖如下

在這里插入圖片描述

FlowLayout完成代碼

public class FlowLayout extends ViewGroup {

public FlowLayout(Context context) {

super(context);

}

public FlowLayout(Context context, AttributeSet attrs) {

super(context, attrs);

}

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

super(context, attrs, defStyleAttr);

}

@Override

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

super.onMeasure(widthMeasureSpec, heightMeasureSpec);

int widthMode = MeasureSpec.getMode(widthMeasureSpec);

int heightMode = MeasureSpec.getMode(heightMeasureSpec);

int width = MeasureSpec.getSize(widthMeasureSpec);

int height = MeasureSpec.getSize(heightMeasureSpec);

int lineWidth = 0;

int resultWidth = 0;

int resultHeight = 0;

int lineCount = 1;

for (int i = 0; i < getChildCount(); i++) {

View childAt = getChildAt(i);

measureChild(childAt, widthMeasureSpec, heightMeasureSpec);

MarginLayoutParams layoutParams = (MarginLayoutParams) childAt.getLayoutParams();

lineWidth += childAt.getMeasuredWidth() + layoutParams.leftMargin + layoutParams.rightMargin;

if (lineWidth > width) {

resultWidth = Math.max(lineWidth - childAt.getMeasuredWidth(), resultWidth);

// 換行

lineWidth = 0;

lineCount++;

}

}

View lastChild = getChildAt(getChildCount() - 1);

MarginLayoutParams marginParams = (MarginLayoutParams) lastChild.getLayoutParams();

resultHeight += (lastChild.getMeasuredHeight() + marginParams.topMargin + marginParams.bottomMargin) * (lineCount + 2);

resultWidth += getPaddingLeft() + getPaddingRight();

resultHeight += getPaddingTop() + getPaddingBottom();

setMeasuredDimension(widthMode == MeasureSpec.AT_MOST ? resultWidth : width,

heightMode == MeasureSpec.AT_MOST ? resultHeight : height);

}

@Override

protected void onDraw(Canvas canvas) {

super.onDraw(canvas);

}

@Override

protected void onLayout(boolean changed, int l, int t, int r, int b) {

int parentWidth = getMeasuredWidth();

int left, top, right, bottom;

int lineWidth = 0;

int lineHeight = 0;

for (int i = 0; i < getChildCount(); i++) {

View childAt = getChildAt(i);

int childWidth = childAt.getMeasuredWidth();

int childHeight = childAt.getMeasuredHeight();

MarginLayoutParams layoutParams = (MarginLayoutParams) childAt.getLayoutParams();

left = lineWidth + layoutParams.leftMargin;

right = left + childWidth + layoutParams.rightMargin;

top = lineHeight + layoutParams.topMargin;

bottom = top + childHeight + layoutParams.bottomMargin;

if (right > parentWidth) {

lineWidth = 0;

lineHeight += childHeight + layoutParams.topMargin + layoutParams.bottomMargin;

left = lineWidth + layoutParams.leftMargin;

right = left + childWidth + layoutParams.rightMargin;

top = lineHeight + layoutParams.topMargin;

bottom = top + childHeight + layoutParams.bottomMargin;

}

childAt.layout(left, top, right, bottom);

lineWidth += childWidth + layoutParams.rightMargin + layoutParams.leftMargin;

}

}

@Override

public LayoutParams generateLayoutParams(AttributeSet attrs) {

return new MarginLayoutParams(getContext(), attrs);

}

}

總結

以上是生活随笔為你收集整理的android面试自定义view,资深面试官:自定义View的实现方式,你知道几种?的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

主站蜘蛛池模板: 天天艹av| 国产综合视频一区二区 | 成人黄色片视频 | 麻豆视频在线免费看 | 国产成人短视频 | 天天综合干 | 婷婷激情五月网 | youjizz视频 | 四季av中文字幕一区 | 国产成人无码a区在线观看视频 | 亚洲男女视频 | 青青国产 | 久久久看片| 欧美黑人又粗又大高潮喷水 | 双性懵懂美人被强制调教 | 波多野吉衣在线视频 | 欧美亚洲二区 | 少妇高潮网站 | 超碰999| 久久精品国产亚洲AV成人婷婷 | 色妞网| 免费欧美黄色 | missav|免费高清av在线看 | 亚洲天堂五月天 | 精品一区免费观看 | 自拍偷拍一区 | 男女日批免费视频 | 男女视频免费网站 | 成人免费看黄 | 亚洲AV无码成人精品一区 | 三上悠亚影音先锋 | 欧美精品第二页 | 六月激情网 | 老司机在线精品视频 | 色偷偷av一区二区三区 | 日韩av成人在线 | 福利精品在线 | 欧美日韩国产免费一区二区三区 | 精品国产成人 | 色综合图区 | 一区二区在线免费 | 中文字幕一区三区 | 中文字幕电影av | 综合网在线 | 日本美女动态图 | 可以免费看的黄色网址 | 一区二区黄色片 | 超碰一级片 | 美女隐私无遮挡网站 | 在线观看亚洲大片短视频 | www.69pao.com | 免费操片 | 亚州综合网| 狠狠干一区二区 | 福利在线视频观看 | 青青草逼 | 日韩第一区 | 精品在线视频免费 | 香蕉视频国产 | av资源在线免费观看 | 亚洲一区国产一区 | 午夜视频www | 国产乱来 | 奇米色综合 | 激烈的性高湖波多野结衣 | 免费成人av片| 网址在线观看你懂的 | 国产三级大片 | 日本xxx高清 | 亚洲黄色免费网站 | 欧美日韩一区二区区 | 婷婷影院在线观看 | 精品三级国产 | 男生和女生靠逼视频 | 99自拍偷拍| 香蕉一级视频 | 久久精品99久久久久久久久 | 国产精品高潮呻吟久久av黑人 | 免费黄色片子 | 视频精品一区 | 99免费观看 | r级无码视频在线观看 | 99久久精品国产成人一区二区 | 看a网站| 神马久久午夜 | 激情欧美一区二区三区精品 | 中日韩免费视频 | 国产亚洲视频在线 | 亚洲乱码国产乱码精品精的特点 | 97色网| 国产综合在线观看 | 伊人77| a级成人毛片 | 亚洲蜜桃视频 | 国产精品传媒视频 | 久久久久久无码精品人妻一区二区 | 超碰av免费 | 日本五十路 | 极品美女被c |