13問13答全面學(xué)習(xí)Android View繪制
本文通過13問13答學(xué)習(xí)Android View繪制,供大家參考,具體內(nèi)容如下
1.View的繪制流程分幾步,從哪開始?哪個(gè)過程結(jié)束以后能看到view?
答:從ViewRoot的performTraversals開始,經(jīng)過measure,layout,draw 三個(gè)流程。draw流程結(jié)束以后就可以在屏幕上看到view了。
2.view的測(cè)量寬高和實(shí)際寬高有區(qū)別嗎?
答:基本上百分之99的情況下都是可以認(rèn)為沒有區(qū)別的。有兩種情況,有區(qū)別。第一種 就是有的時(shí)候會(huì)因?yàn)槟承┰?view會(huì)多次測(cè)量,那第一次測(cè)量的寬高 肯定和最后實(shí)際的寬高 是不一定相等的,但是在這種情況下
最后一次測(cè)量的寬高和實(shí)際寬高是一致的。此外,實(shí)際寬高是在layout流程里確定的,我們可以在layout流程里 將實(shí)際寬高寫死 寫成硬編碼,這樣測(cè)量的寬高和實(shí)際寬高就肯定不一樣了,雖然這么做沒有意義 而且也不好。
3.view的measureSpec 由誰(shuí)決定?頂級(jí)view呢?
答:由view自己的layoutparams和父容器 一起決定自己的measureSpec。一旦確定了spec,onMeasure中就可以確定view的寬高了。
頂級(jí)view就稍微特殊一點(diǎn),對(duì)于decorView的測(cè)量在ViewRootImpl的源碼里。
//desire的這2個(gè)參數(shù)就代表屏幕的寬高,
childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
//decorView的measureSpec就是在這里確定的,其實(shí)比普通view的measurespec要簡(jiǎn)單的多
//代碼就不分析了 一目了然的東西
private static int getRootMeasureSpec(int windowSize, int rootDimension) {
int measureSpec;
switch (rootDimension) {
case ViewGroup.LayoutParams.MATCH_PARENT:
// Window can't resize. Force root view to be windowSize.
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
break;
case ViewGroup.LayoutParams.WRAP_CONTENT:
// Window can resize. Set max size for root view.
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
break;
default:
// Window wants to be an exact size. Force root view to be that size.
measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
break;
}
return measureSpec;
}
4.對(duì)于普通view來說,他的measure過程中,與父view有關(guān)嗎?如果有關(guān),這個(gè)父view也就是viewgroup扮演了什么角色?
答:看源碼:
//對(duì)于普通view的measure來說 是由這個(gè)view的 父view ,也就是viewgroup來觸發(fā)的。
//也就是下面這個(gè)measureChildWithMargins方法
protected void measureChildWithMargins(View child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
//第一步 先取得子view的 layoutParams 參數(shù)值
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
//然后開始計(jì)算子view的spec的值,注意這里看到 計(jì)算的時(shí)候除了要用子view的 layoutparams參數(shù)以外
//還用到了父view 也就是viewgroup自己的spec的值
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
+ widthUsed, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
+ heightUsed, lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
//這個(gè)算view的spec的方法 看上去一大串 但是真的邏輯非常簡(jiǎn)單 就是根據(jù)父親viewgroup
//的meaurespec 同時(shí)還有view自己的params來確定 view自己的measureSpec。
//注意這里的參數(shù)是padding,這個(gè)值的含義是 父容器已占用的控件的大小 所以view的Specsize
//的值 你們可以看到 是要減去這個(gè)padding的值的??偞笮?已經(jīng)用的 =可用的。 很好理解。
//然后就是下面的switch邏輯 要自己梳理清楚。其實(shí)也不難,主要是下面幾條原則
//如果view采用固定寬高,也就是寫死的數(shù)值那種。那就不管父親的spec的值了,view的spec 就肯定是exactly 并且大小遵循layout參數(shù)里設(shè)置的大小。
//如果view的寬高是match_parent ,那么就要看父容器viewgroup的 spec的值了,如果父view的spec是exactly模式,
//那view也肯定是exactly,并且大小就是父容器剩下的空間。如果父容器是at_most模式,那view也是at_most 并且不會(huì)超過剩余空間大小
//如果view的寬高是wrap_content, 那就不管父容器的spec了,view的spec一定是at_most 并且不會(huì)超過父view 剩余空間的大小。
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
int specMode = MeasureSpec.getMode(spec);
int specSize = MeasureSpec.getSize(spec);
int size = Math.max(0, specSize - padding);
int resultSize = 0;
int resultMode = 0;
switch (specMode) {
// Parent has imposed an exact size on us
case MeasureSpec.EXACTLY:
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size. So be it.
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// Parent has imposed a maximum size on us
case MeasureSpec.AT_MOST:
if (childDimension >= 0) {
// Child wants a specific size... so be it
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size, but our size is not fixed.
// Constrain child to not be bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// Parent asked to see how big we want to be
case MeasureSpec.UNSPECIFIED:
if (childDimension >= 0) {
// Child wants a specific size... let him have it
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size... find out how big it should
// be
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size.... find out how
// big it should be
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
}
break;
}
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
5.view的meaure和onMeasure有什么關(guān)系?
答:看源碼:
//view的measure是final 方法 我們子類無法修改的。
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
boolean optical = isLayoutModeOptical(this);
if (optical != isLayoutModeOptical(mParent)) {
Insets insets = getOpticalInsets();
int oWidth = insets.left + insets.right;
int oHeight = insets.top + insets.bottom;
widthMeasureSpec = MeasureSpec.adjust(widthMeasureSpec, optical ? -oWidth : oWidth);
heightMeasureSpec = MeasureSpec.adjust(heightMeasureSpec, optical ? -oHeight : oHeight);
}
// Suppress sign extension for the low bytes
long key = (long) widthMeasureSpec << 32 | (long) heightMeasureSpec & 0xffffffffL;
if (mMeasureCache == null) mMeasureCache = new LongSparseLongArray(2);
if ((mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ||
widthMeasureSpec != mOldWidthMeasureSpec ||
heightMeasureSpec != mOldHeightMeasureSpec) {
// first clears the measured dimension flag
mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;
resolveRtlPropertiesIfNeeded();
int cacheIndex = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ? -1 :
mMeasureCache.indexOfKey(key);
if (cacheIndex < 0 || sIgnoreMeasureCache) {
// measure ourselves, this should set the measured dimension flag back
onMeasure(widthMeasureSpec, heightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
} else {
long value = mMeasureCache.valueAt(cacheIndex);
// Casting a long to int drops the high 32 bits, no mask needed
setMeasuredDimensionRaw((int) (value >> 32), (int) value);
mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}
// flag not set, setMeasuredDimension() was not invoked, we raise
// an exception to warn the developer
if ((mPrivateFlags & PFLAG_MEASURED_DIMENSION_SET) != PFLAG_MEASURED_DIMENSION_SET) {
throw new IllegalStateException("View with id " + getId() + ": "
+ getClass().getName() + "#onMeasure() did not set the"
+ " measured dimension by calling"
+ " setMeasuredDimension()");
}
mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
}
mOldWidthMeasureSpec = widthMeasureSpec;
mOldHeightMeasureSpec = heightMeasureSpec;
mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 |
(long) mMeasuredHeight & 0xffffffffL); // suppress sign extension
}
//不過可以看到的是在measure方法里調(diào)用了onMeasure方法
//所以就能知道 我們?cè)谧远xview的時(shí)候一定是重寫這個(gè)方法!
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
6.簡(jiǎn)要分析view的measure流程?
答:先回顧問題4,viewgroup 算出子view的spec以后 會(huì)調(diào)用子view的measure方法,而子view的measure方法 我們問題5也看過了實(shí)際上是調(diào)用的onMeasure方法
所以我們只要分析好onMeasure方法即可,注意onMeasure方法的參數(shù) 正是他的父view算出來的那2個(gè)spec的值(這里view的measure方法會(huì)把這個(gè)spec里的specSize值做略微的修改 這個(gè)部分 不做分析 因?yàn)閙easure方法修改specSize的部分很簡(jiǎn)單)。
//可以看出來這個(gè)就是setMeasuredDimension方法的調(diào)用 這個(gè)方法看名字就知道就是確定view的測(cè)量寬高的
//所以我們分析的重點(diǎn)就是看這個(gè)getDefaultSize 方法 是怎么確定view的測(cè)量寬高的
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
//這個(gè)方法特別簡(jiǎn)單 基本可以認(rèn)為就是近似的返回spec中的specSize,除非你的specMode是UNSPECIFIED
//UNSPECIFIED 這個(gè)一般都是系統(tǒng)內(nèi)部測(cè)量才用的到,這種時(shí)候返回size 也就是getSuggestedMinimumWidth的返回值
public static int getDefaultSize(int size, int measureSpec) {
int result = size;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
case MeasureSpec.UNSPECIFIED:
result = size;
break;
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
//跟view的背景相關(guān) 這里不多做分析了
protected int getSuggestedMinimumWidth() {
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}
7.自定義view中 如果onMeasure方法 沒有對(duì)wrap_content 做處理 會(huì)發(fā)生什么?為什么?怎么解決?
答:如果沒有對(duì)wrap_content做處理 ,那即使你在xml里設(shè)置為wrap_content.其效果也和match_parent相同。看問題4的分析。我們可以知道view自己的layout為wrap,那mode就是at_most(不管父親view是什么specmode).
這種模式下寬高就是等于specSize(getDefaultSize函數(shù)分析可知),而這里的specSize顯然就是parentSize的大小。也就是父容器剩余的大小。那不就和我們直接設(shè)置成match_parent是一樣的效果了么?
解決方式就是在onMeasure里 針對(duì)wrap 來做特殊處理 比如指定一個(gè)默認(rèn)的寬高,當(dāng)發(fā)現(xiàn)是wrap_content 就設(shè)置這個(gè)默認(rèn)寬高即可。
8.ViewGroup有onMeasure方法嗎?為什么?
答:沒有,這個(gè)方法是交給子類自己實(shí)現(xiàn)的。不同的viewgroup子類 肯定布局都不一樣,那onMeasure索性就全部交給他們自己實(shí)現(xiàn)好了。
9.為什么在activity的生命周期里無法獲得測(cè)量寬高?有什么方法可以解決這個(gè)問題嗎?
答:因?yàn)閙easure的過程和activity的生命周期 沒有任何關(guān)系。你無法確定在哪個(gè)生命周期執(zhí)行完畢以后 view的measure過程一定走完??梢試L試如下幾種方法 獲取view的測(cè)量寬高。
//重寫activity的這個(gè)方法
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
if (hasFocus) {
int width = tv.getMeasuredWidth();
int height = tv.getMeasuredHeight();
Log.v("burning", "width==" + width);
Log.v("burning", "height==" + height);
}
}
或者重寫這個(gè)方法
@Override
protected void onStart() {
super.onStart();
tv.post(new Runnable() {
@Override
public void run() {
int width = tv.getMeasuredWidth();
int height = tv.getMeasuredHeight();
}
});
}
再或者:
@Override
protected void onStart() {
super.onStart();
ViewTreeObserver observer = tv.getViewTreeObserver();
observer.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
int width = tv.getMeasuredWidth();
int height = tv.getMeasuredHeight();
tv.getViewTreeObserver().removeOnGlobalLayoutListener(this);
}
});
}
10.layout和onLayout方法有什么區(qū)別?
答:layout是確定本身view的位置 而onLayout是確定所有子元素的位置。layout里面 就是通過serFrame方法設(shè)設(shè)定本身view的 四個(gè)頂點(diǎn)的位置。這4個(gè)位置以確定 自己view的位置就固定了
然后就調(diào)用onLayout來確定子元素的位置。view和viewgroup的onlayout方法都沒有寫。都留給我們自己給子元素布局
11.draw方法 大概有幾個(gè)步驟?
答: 一共是4個(gè)步驟, 繪制背景---------繪制自己--------繪制chrildren----繪制裝飾。
12.setWillNotDraw方法有什么用?
答:這個(gè)方法在view里。
/**
* If this view doesn't do any drawing on its own, set this flag to
* allow further optimizations. By default, this flag is not set on
* View, but could be set on some View subclasses such as ViewGroup.
*
* Typically, if you override {@link #onDraw(android.graphics.Canvas)}
* you should clear this flag.
*
* @param willNotDraw whether or not this View draw on its own
*/
public void setWillNotDraw(boolean willNotDraw) {
setFlags(willNotDraw ? WILL_NOT_DRAW : 0, DRAW_MASK);
}
用于設(shè)置標(biāo)志位的 也就是說 如果你的自定義view 不需要draw的話,就可以設(shè)置這個(gè)方法為true。這樣系統(tǒng)知道你這個(gè)view 不需要draw 可以優(yōu)化執(zhí)行速度。viewgroup 一般都默認(rèn)設(shè)置這個(gè)為true,因?yàn)関iewgroup多數(shù)都是只負(fù)責(zé)布局
不負(fù)責(zé)draw的。而view 這個(gè)標(biāo)志位 默認(rèn)一般都是關(guān)閉的。
13.自定義view 有哪些需要注意的點(diǎn)?
答:主要是要處理wrap_content 和padding。否則xml 那邊設(shè)置這2個(gè)屬性就根本沒用了。還有不要在view中使用handler 因?yàn)槿思乙呀?jīng)提供了post方法。如果是繼承自viewGroup,那在onMeasure和onLayout里面 也要考慮
padding和layout的影響。也就是說specSize 要算一下 。最后就是如果view的動(dòng)畫或者線程需要停止,可以考慮在onDetachedFromWindow里面來做。
針對(duì)上述的幾點(diǎn),給出幾個(gè)簡(jiǎn)單的自定義view 供大家理解。
給出一個(gè)圓形的view 范例:
package com.example.administrator.motioneventtest;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.View;
/**
* Created by Administrator on 2016/2/4.
*/
public class CircleView extends View {
private int mColor = Color.RED;
private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private void init() {
mPaint.setColor(mColor);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
//處理為wrap_content時(shí)的情況
if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(200, 200);
} else if (widthSpecMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(200, heightSpecSize);
} else if (heightSpecMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(widthSpecSize, 200);
}
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//處理padding的情況
final int paddingLeft = getPaddingLeft();
final int paddingRight = getPaddingRight();
final int paddingTop = getPaddingTop();
final int paddingBottom = getPaddingBottom();
int width = getWidth() - paddingLeft - paddingRight;
int height = getHeight() - paddingTop - paddingBottom;
int radius = Math.min(width, height) / 2;
canvas.drawCircle(paddingLeft + width / 2, paddingTop + height / 2, radius, mPaint);
}
public CircleView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
public CircleView(Context context) {
super(context);
init();
}
public CircleView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
}
然后下面再給出一個(gè)范例,稍微復(fù)雜一點(diǎn)是自定義viewgroup了(主要是加強(qiáng)對(duì)onMeasure和onLayout的理解), 需求如下:
一個(gè)水平的viewgroup,內(nèi)部的子元素 為了簡(jiǎn)單 我們假定他們的寬高都是一樣的。來寫一個(gè)這樣的簡(jiǎn)單的viewgroup。
package com.example.administrator.motioneventtest;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
/**
* Created by Administrator on 2016/2/4.
*/
//這里我們只處理了padding的狀態(tài) 沒有處理margin的狀態(tài),子view的margin 對(duì)measure和layout的影響
//就留給讀者自己完成了
public class CustomHorizontalLayout extends ViewGroup {
//設(shè)置默認(rèn)的控件最小是多少 這里不提供自定義屬性了 寫死在代碼里 你們可以自行拓展
final int minHeight = 0;
final int minWidth = 0;
public CustomHorizontalLayout(Context context) {
super(context);
}
public CustomHorizontalLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CustomHorizontalLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int measureWidth = 0;
int measureHeight = 0;
final int childCount = getChildCount();
measureChildren(widthMeasureSpec, heightMeasureSpec);
int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
final View childView = getChildAt(0);
final int paddingLeft = getPaddingLeft();
final int paddingRight = getPaddingRight();
final int paddingTop = getPaddingTop();
final int paddingBottom = getPaddingBottom();
//沒有子控件 時(shí) 我們的寬高要作特殊處理
if (childCount == 0) {
//當(dāng)沒有子控件時(shí),如果長(zhǎng)寬有一個(gè)為wrap 那么就讓這個(gè)控件以最小的形式展現(xiàn)
//這里我們最小設(shè)置為0
if (widthSpecMode == MeasureSpec.AT_MOST || heightSpecMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(minWidth, minHeight);
} else {
//否則根據(jù)我們的layout屬性來
setMeasuredDimension(getLayoutParams().width, getLayoutParams().height);
}
} else if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) {
measureWidth = childView.getMeasuredWidth() * childCount;
measureHeight = childView.getMeasuredHeight();
setMeasuredDimension(paddingLeft + measureWidth + paddingRight, paddingTop + measureHeight + paddingBottom);
} else if (heightSpecMode == MeasureSpec.AT_MOST) {
measureHeight = childView.getMeasuredHeight();
setMeasuredDimension(paddingLeft + paddingRight + widthSpecSize, paddingTop + paddingBottom + measureHeight);
} else if (widthSpecMode == MeasureSpec.AT_MOST) {
measureWidth = childView.getMeasuredWidth() * childCount;
setMeasuredDimension(paddingLeft + paddingRight + measureWidth, paddingTop + paddingBottom + heightSpecSize);
}
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
final int paddingLeft = getPaddingLeft();
final int paddingRight = getPaddingRight();
final int paddingTop = getPaddingTop();
final int paddingBottom = getPaddingBottom();
//左邊初始位置為0
int childLeft = 0 + paddingLeft;
final int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
final View childView = getChildAt(i);
if (childView.getVisibility() != View.GONE) {
final int childWidth = childView.getMeasuredWidth();
childView.layout(childLeft, 0 + paddingTop, childLeft + childWidth, paddingTop + childView.getMeasuredHeight());
childLeft += childWidth;
}
}
}
}
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助。
相關(guān)文章
Android超詳細(xì)講解組件AdapterView的使用
AdapterView組件是一組重要的組件,AdapterView本身是一個(gè)抽象基類,它派生的子類在用法上十分相似,從AdapterView派生出的三個(gè)子類:AdsListView、AdsSpinner、AdapterViewAnimator,這3個(gè)子類依然是抽象的,實(shí)際運(yùn)用時(shí)需要它們的子類2022-03-03
Android獲取當(dāng)前已連接的wifi信號(hào)強(qiáng)度的方法
這篇文章主要介紹了Android獲取當(dāng)前已連接的wifi信號(hào)強(qiáng)度的方法,主要通過系統(tǒng)自帶的WifiInfo類實(shí)現(xiàn),需要的朋友可以參考下2014-09-09
Android ShimmerLayout實(shí)現(xiàn)微光效果解析
這篇文章主要為大家詳細(xì)介紹了Android ShimmerLayout實(shí)現(xiàn)微光效果,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-03-03
Android應(yīng)用中炫酷的橫向和環(huán)形進(jìn)度條的實(shí)例分享
這篇文章主要介紹了Android應(yīng)用中炫酷的橫向和圓形進(jìn)度條的實(shí)例分享,文中利用了一些GitHub上的插件進(jìn)行改寫,也是一片很好的二次開發(fā)教學(xué),需要的朋友可以參考下2016-04-04
android打開應(yīng)用所在的市場(chǎng)頁(yè)面進(jìn)行評(píng)分操作的方法
這篇文章主要介紹了android打開應(yīng)用所在的市場(chǎng)頁(yè)面進(jìn)行評(píng)分操作的方法,涉及Android操作市場(chǎng)頁(yè)面評(píng)分效果的相關(guān)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-04-04
Android自定義控件RatingBar調(diào)整字體大小
這篇文章主要為大家詳細(xì)介紹了Android自定義控件RatingBar調(diào)整字體大小的相關(guān)資料,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-03-03
Android NDK開發(fā)的環(huán)境搭建與簡(jiǎn)單示例
本文主要介紹Android NDK的知識(shí),這里整理了相關(guān)資料,來說明如何搭建相應(yīng)環(huán)境和簡(jiǎn)單實(shí)例,幫助大家理解,有興趣的小伙伴可以參考下2016-09-09
Android實(shí)現(xiàn)伸縮彈力分布菜單效果的示例
本文介紹下在Android中實(shí)現(xiàn)伸縮彈力分布菜單效果。這種效果比較炫酷,有需要的朋友可以參考一下。2016-10-10
Android基于OpenCV實(shí)現(xiàn)圖像金字塔
圖像金字塔是圖像中多尺度表達(dá)的一種,最主要用于圖像的分割,是一種以多分辨率來解釋圖像的有效但概念簡(jiǎn)單的結(jié)構(gòu)。本文講解Android基于OpenCV實(shí)現(xiàn)圖像金字塔的步驟2021-06-06

