編輯:關於Android編程
Android中對View的更新有很多種方式,使用時要區分不同的應用場合。
1.不使用多線程和雙緩沖
這種情況最簡單,一般只是希望在View發生改變時對UI進行重繪。你只需顯式地調用View對象中的invalidate(){關於invalidate的解釋:當調用線程處於空閒狀態時,會調用onDraw,刷新界面,也就是說,該函數僅是標記當前界面過期,並不直接負責刷新界面;}方法即可。系統會自動調用View的onDraw()方法。
2.使用多線程但不使用雙緩沖
這種情況需要開啟新的線程,新開的線程就不好訪問View對象了。強行訪問的話會報:android.view.ViewRoot$CalledFromWrongThreadException:Only the original thread that created a view hierarchy can touch its views.
這時候你需要創建一個繼承了android.os.Handler的子類,並重寫handleMessage(Message msg)方法。android.os.Handler是能發送和處理消息的,你需要在Activity中發出更新UI的消息,然後在Handler(可以使用匿名內部類)中處理消息(因為匿名內部類可以訪問父類變量, 你可以直接調用View對象中的invalidate()方法 )。也就是說:在新線程創建並發送一個Message,然後再主線程中捕獲、處理該消息。
3.使用多線程和雙緩沖
Android中SurfaceView是View的子類,她同時也實現了雙緩沖。可以定義一個她的子類並實現SurfaceHolder.Callback接口。由於實現SurfaceHolder.Callback接口,新線程就不需要android.os.Handler幫忙了。SurfaceHolder中lockCanvas()方法可以鎖定畫布,繪制完新的圖像後調用unlockCanvasAndPost(canvas)解鎖(顯示)
先看看源代碼對SurfaceHolder接口的描述
/**
* 允許你控制surface view的大小、樣式,編輯像素或監視surface的改變,典型的運用於SurfaceView中,需要注意 * lockCanvas方法和Callback.surfaceCreated方法*/再看SurfaceHolder.Callback的描述
/**
* A client may implement this interface to receive information about
* changes to the surface. When used with a {@link SurfaceView}, the
* Surface being held is only available between calls to
* {@link #surfaceCreated(SurfaceHolder)} and
* {@link #surfaceDestroyed(SurfaceHolder)}. The Callback is set with
* {@link SurfaceHolder#addCallback SurfaceHolder.addCallback} method.
*/下面是一個繼承自SurfaceView並實現SurfaceHolder.Callback接口的類
public class MySurfaceView extends SurfaceView implements SurfaceHolder.Callback {
private SurfaceHolder holder;
public MySurfaceView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MySurfaceView(Context context) {
super(context);
holder = this.getHolder();
holder.addCallback(this);
this.setLongClickable(true);// 不設置將無法捕捉onFling()事件
setFocusable(true);// 設置鍵盤焦點
setFocusableInTouchMode(true);// 設置觸摸屏焦點
}
protected void paintView(Canvas canvas) { // 自定義方法,類似於onDraw
}public void rePaint() { // 自定義類似於invalidate方法,調用此方法刷新View
Canvas c = null;
try {
c = holder.lockCanvas();
paintView(c);
} finally {
if (c != null) {
holder.unlockCanvasAndPost(c);
}
}
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
Canvas canvas = holder.lockCanvas(null);// 獲取畫布
canvas.drawColor(Color.WHITE);// 設置畫布背景
holder.unlockCanvasAndPost(canvas);// 解鎖畫布,提交畫好的圖像
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height) {
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
}
}------------------------------------------------------View的繪制流程-----------------------------------------------------
View的繪制繪制流程:父View負責刷新、布局、顯示子View;而當子View需要刷新時,則是通知父View來完成。下面通過查看原代碼來驗證
1.子類調用invalidate方法()
/**
* 使當前View無效. 如果View可見,onDraw方法將會在之後某個時間點被調用,這個方法的調用必須在UI線程中,如果在非UI線程中調用需要使用postInvalidate()方法*/
public void invalidate() {
invalidate(true);
}
/**
* invalidate實際上是調用這個方法.drawing的緩存被設置為無效之後一個完整的invalidate將會發生.但是這個功能可以通過設置invalidateCachefalse來跳過無效的步驟當並不需要重新繪制View的時候(例如,一個組件保持著同樣的尺寸和內容)
* @param invalidateCache 這個View的緩存是否應該被設置為無效,通常是false表示要進行全部繪制,但是可能設置為true當View的Content和dimension都沒有改變時.
*/
void invalidate(boolean invalidateCache) {
if (skipInvalidate()) {
return;
}
if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)) == (PFLAG_DRAWN | PFLAG_HAS_BOUNDS) ||
(invalidateCache && (mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID) ||
(mPrivateFlags & PFLAG_INVALIDATED) != PFLAG_INVALIDATED || isOpaque() != mLastIsOpaque) {
// ......final AttachInfo ai = mAttachInfo; // 獲取匹配
final ViewParent p = mParent; // 獲取父類對象
// noinspection PointlessBooleanExpression,ConstantConditions
if (!HardwareRenderer.RENDER_DIRTY_REGIONS) {
if (p != null && ai != null && ai.mHardwareAccelerated) {
p.invalidateChild(this, null);
return;
}
}
if (p != null && ai != null) {
final Rect r = ai.mTmpInvalRect;
r.set(0, 0, mRight - mLeft, mBottom - mTop); // 設置View的尺寸
p.invalidateChild(this, r); // 調用parent對象讓parent對象重繪制child
}
}
}>>2.child View調用invalidate時,首先找到自己父View(View的成員變量mParent記錄自己的父View),然後將AttachInfo中保存的信息告訴父View刷新自己,父View調用invalidateChild函數刷新child View
下面查看ViewGroup中的invalidateChild方法的實現
/**
* 不要調用或重寫此方法,這個方法是用於實現View的繪制層次
*/
public final void invalidateChild(View child, final Rect dirty) {
ViewParent parent = this;
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
// 如果child view繪制的是動畫,我們希望child的mPrivateFlags拷貝到ViewGroup之上
// 並且讓parent確保無效的請求通過
final boolean drawAnimation = (child.mPrivateFlags & PFLAG_DRAW_ANIMATION)
== PFLAG_DRAW_ANIMATION;
// ...final int[] location = attachInfo.mInvalidateChildLocation;
location[CHILD_LEFT_INDEX] = child.mLeft;
location[CHILD_TOP_INDEX] = child.mTop;
// ...do {
View view = null;
if (parent instanceof View) {
view = (View) parent;
}
if (drawAnimation) {
if (view != null) {
view.mPrivateFlags |= PFLAG_DRAW_ANIMATION;
} else if (parent instanceof ViewRootImpl) {
((ViewRootImpl) parent).mIsAnimating = true;
}
}
// If the parent is dirty opaque or not dirty, mark it dirty with the opaque
// flag coming from the child that initiated the invalidate
if (view != null) {
if ((view.mViewFlags & FADING_EDGE_MASK) != 0 &&
view.getSolidColor() == 0) {
opaqueFlag = PFLAG_DIRTY;
}
if ((view.mPrivateFlags & PFLAG_DIRTY_MASK) != PFLAG_DIRTY) {
view.mPrivateFlags = (view.mPrivateFlags & ~PFLAG_DIRTY_MASK) | opaqueFlag;
}
}
parent = parent.invalidateChildInParent(location, dirty); // 轉到第三步,調用此方法層層刷新View
if (view != null) {
// Account for transform on current parent
Matrix m = view.getMatrix();
if (!m.isIdentity()) {
RectF boundingRect = attachInfo.mTmpTransformRect;
boundingRect.set(dirty);
m.mapRect(boundingRect);
dirty.set((int) (boundingRect.left - 0.5f),
(int) (boundingRect.top - 0.5f),
(int) (boundingRect.right + 0.5f),
(int) (boundingRect.bottom + 0.5f));
}
}
} while (parent != null);
}
}3>>.調用invalidateChildInParent函數依次層層刷新
/**
* 這個方法返回null如果ViewGroup已經沒有父View了,
* 或者如果這個ViewGrop已經全部被設置為無效,或者當前View的需要刷新的rectangle區域與ViewGroup不相交
*/
public ViewParent invalidateChildInParent(final int[] location, final Rect dirty) {
if ((mPrivateFlags & PFLAG_DRAWN) == PFLAG_DRAWN ||
(mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID) {
if ((mGroupFlags & (FLAG_OPTIMIZE_INVALIDATE | FLAG_ANIMATION_DONE)) !=
FLAG_OPTIMIZE_INVALIDATE) {
dirty.offset(location[CHILD_LEFT_INDEX] - mScrollX,
location[CHILD_TOP_INDEX] - mScrollY); // 根據父View的位置,偏移刷新區域
final int left = mLeft;
final int top = mTop;
if ((mGroupFlags & FLAG_CLIP_CHILDREN) == FLAG_CLIP_CHILDREN) {
if (!dirty.intersect(0, 0, mRight - left, mBottom - top)) { // 計算實際可刷新區域
dirty.setEmpty();
}
}
mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
location[CHILD_LEFT_INDEX] = left;
location[CHILD_TOP_INDEX] = top;
if (mLayerType != LAYER_TYPE_NONE) {
mPrivateFlags |= PFLAG_INVALIDATED;
mLocalDirtyRect.union(dirty);
}
return mParent;
} else {
mPrivateFlags &= ~PFLAG_DRAWN & ~PFLAG_DRAWING_CACHE_VALID;
location[CHILD_LEFT_INDEX] = mLeft;
location[CHILD_TOP_INDEX] = mTop;
if ((mGroupFlags & FLAG_CLIP_CHILDREN) == FLAG_CLIP_CHILDREN) {
dirty.set(0, 0, mRight - mLeft, mBottom - mTop);
} else {
// in case the dirty rect extends outside the bounds of this container
dirty.union(0, 0, mRight - mLeft, mBottom - mTop);
}
if (mLayerType != LAYER_TYPE_NONE) {
mPrivateFlags |= PFLAG_INVALIDATED;
mLocalDirtyRect.union(dirty);
}
return mParent;
}
}
return null;
}
我們在平時做開發的時候,免不了會用到各種各樣的對話框,相信有過其他平台開發經驗的朋友都會知道,大部分的平台都只提供了幾個最簡單的實現,如果我們想實現自己特定需求的對話框,
前提:手機已經root; 1.手機連接電腦,打開Cmd,運行命令adb shell;//因為android用的Linux內核,很多linux的命令,在Android也可以
前面為大家講過計時器的順時針的兩種方法,在錄制視頻等操作中頗有使用,今天就給大家帶來倒計時實現的兩種方式。雖然最近寫的都比較簡單和基礎,不過簡單不代表熟悉,基礎不代表就會
概括這篇博客裡面就來實踐下。在上一篇博客裡面說到了OkHttp類似HttpUrlConnection。按這樣說的話,我們在項目中肯定還是要封裝一層。如果嫌封裝麻煩的話,也