編輯:關於Android編程
基本概念
ViewRoot對應於ViewRootImpl類,是連接WindowManager和DecorView的紐帶,View的三大流程均是通過ViewRoot來完成的。在ActivityThread中,當Activity對象被創建完成後,會將DecorView添加到View中。同時,會創建ViewRootImpl對象,並將ViewTootImpl對象和DecorView建立關聯。
源碼如下:
root = new ViewRootImpl(view,getContext(),dispaly);
root.setView(view,panelParentView);
View的繪制流程是從ViewRoot的performTraversals方法開始的,它經過measure、layout和draw三個過程才能最終將一個View繪制出來,其中measure用來測量View的寬和高,layout用來確定View在父容器中的位置,而draw則負責將View繪制在屏幕上。
performMeasure ——> measur ——> onMeasure
↓
preformLayout ——> layout ——> onLayout
↓
performDraw ——> draw ——> onDraw
MeasureSpec翻譯為”測量規格”。MeasureSpec在很大程度上決定一個View的尺寸規格,之所以說是很大程度上是因為這個過程還受父容器的影響,父容器影響View的MeasureSpec。在測量過程中,系統會將View的LayoutParams根據這個measureSpec來測量出View的寬/高,不一定等於View的最終寬/高。
MeasureSpec代表一個32位int值,高2位代表SpecMode,低30位代表SpecSize。SpecMode是指測量模式,SpecSize是指某種測量模式下的規格大小。
部分源碼:
private static final int MODE_SHIFT = 30;
private static final int MODE_MASK = 0x3 << MODE_SHIFT;
public static final int UNSPECIFIED = 0 << MODE_SHIFT;
public static final int EXACTLY = 1 << MODE_SHIFT;
public static final int AT_MOST = 2 << MODE_SHIFT;
public stataic int makeMeasureSpec(int size,int mode){
if( sUseBrokenMakeMeasureSpec){
retrurn size + mode ;
}else{
return (size & ~Mode_MASK)|(mode & MODE_MASK)
}
}
public static int getMode(int measureSpec){
return (measureSpec & MODE_MASK);
}
public static int getSize(int measureSpec){
rerurn (measureSpec & ~MODE_MASK);
}
MeaureSpec通過將SpecMode個SpecSize打包打包一個int值避免過多的對象內存分配,為了方便操作,其提供了打包和解包方法。SpecMode和SpecSize可以打包一個MeasureSpec,而一個MeasureSpec可以通過解包的形式來得出原始的SpecMode和SpecSize,需要注意的是這裡提到的MeasureSpec是指MeaureSpec所代表的int值,而非MeasureSpec本身。
SpecMode
UNSPECIFIED
父容器不對View有任何限制,要多大給多大,這種情況一般用於系統內部,表示一種測量狀態。
EXACTLY
父容器已經檢測出View所要的精確的大小,這個時候View的最終大小就是SpecSize所指定的值。對應於LayoutParams中的match_parent和具體的數值這兩種模式。
AT_MOST
父容器指定了一個可用大小的SpecSize,View的大小不能大於這個值,具體是什麼值要看不同View的具體實現。它對應LayoutParams中的warp_content。
系統內部是通過MeasureSpec來進行View的測量,但正常情況下使用View指定MeasureSpec,可以給View設置LayoutParams。在View測量的時候,系統會將LayoutParams在父容器的約束下轉換成對應的MeasureSpec,然後在根據這個MeasureSpec來確定View測量後的寬/高。 注意: MeasureSpec不是唯一由LayoutParams決定的,LayoutParams需要父容器一起才能決定View的MeasureSpec,從而進一步決定View的寬/高。
另外,對於頂級的View也就是DecorView普通View來說,MeasureSpec的轉換過程略有不同。對於DecorView,其MeasureSpec由窗口的尺寸和其自身的LayoutParams來共同確定;對於普通View,其MeasureSpec一旦確定後,onMeasure中就可以確定View的測量寬/高。
DecorView在ViewRootImpl中的measureHierarchy方法有如下一段代碼,展示了DecorView的MeasureSpec的創建過程,其中desiredWindowWidth和desiredWindowHeight是屏幕尺寸:
childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth,lp.width);
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight,lp.height);
performMeasure(childWidthMeasureSpec,childHeightMeasureSpec);
getRootMeasureSpec方法的實現:
private static int getRootMeasureSpec(int windowSize,int rootDimension){
int measureSpec;
switch(rootDimension){
case ViewGroup.LayoutParams.MATCH_PARENT:
measureSpec = MeasureSpec.makeMeasureSpec(windowSize,MeasureSpec.EXACTLY);
break;
case ViewGroup.LayoutParams.WRAP_CONTENT:
measureSpec = MeasureSpec.makeMeasureSpec(windowSize,MeasureSpec.AT_MOST);
break;
default:
measureSpec = MeasureSpec.makeMeasureSpec(rootDimension,MeasureSpec.EXACTLY);
break;
}
return measureSpec ;
}
DecorView的MeasureSpec的產生過程就很明確了,具體遵守如下規則,根據它的LayoutParams中的寬/高的參數劃分。
LayoutParams.MATCH_PARENT:精確模式,大小就是窗口的大小 LayoutParams.WRAP_CONTENT:最大模式,大小為LayoutParams中的指定的大小。 固定大小:精確模式,大小LayoutParams中指定的大小。View的工作流程主要是指measure、layout、draw這三大流程,即測量、布局和繪制,其中measure確定View的測量寬/高,layout確定View的最高寬/高和四個頂點的位置,而draw則將View繪制到屏幕上。
measure過程要分情況來看,如果只是一個原始的View,那麼通過measure方法就完成了其測量過程,如果是一個ViewGroup,除了完成自己的測量過程外,還會遍歷去請用所有子元素的measure方法,各個子元素
再遞歸去執行這個過程。
View的measure過程由measure方法來完成,measure方法是一個final類型的方法,這意味著子類不能重寫此方法,在View的measure方法中會調用View的onMeasure方法。
View的onMeasure方法:
protected void onMeasure(int widthMeasureSpec , int heightMeasureSpec){
setMearsureDimension(getDefaultSize(getSuggestedMinimumWidth(),widthMeasureSpec),getDefaultSize(getSuggestedMinimumHeight(),heightMeasureSpec));
}
View的最終的大小是在Layout階段確定。
4.3View的工作原理一遍沒理解多看幾遍。
Draw過程就是將View繪制到屏幕上。
* (1)繪制背景backgroud。draw(canvas)*
* (2)繪制自己(onDraw)*
* (3)繪制children(dispatchDraw)*
* (4)繪制裝飾(onDrawScrollBars)*
根據個人理解分為了四類:
* 1. 繼承View重寫onDraw*
這種方法主要用於實現一些不規則的效果,即這種效果不方便通過布局的組合方式來達到,往往需要靜態或者動態地顯示一些不規則的圖形。很顯然這需要通過繪制的方式來實現,及重寫onDraw方法。采用這種方法需要自己支持wrap_content,並且padding也需要自己處理。
* 2. 繼承ViewGroup派生特殊的Layout*
這種方法主要用於實現自定義的布局,即除了LinearLayout、RelativeLayout、FrameLayout這幾種系統的布局之外,重新定義一中新布局,當某種效果看起來很像幾種View組合在一起的時候,可以采用這種方法來實現。采用這種方式稍微復雜一些。需要合適地處理ViewGroup的測量、布局這兩個過程,並同時處理子元素的測量和布局過程。
* 3. 繼承特定的View(比如TextView)*
一般是用於擴展某種已有的View功能,比如TextView,這種方法比較容易實現。這種方法不需要自己支持wrap_content和padding。
* 4. 繼承特定的ViewGroup(比如LinearLayout)*
當某種效果看起來很像幾種View組合在一起的時候,可以采用這種方法來實現。采用這種方法不需要自己處理ViewGroup的測量和布局這兩個過程。需要注意這種方法和方法2的區別,一般來說方法2能實現的效果方法4都能實現,兩者主要的差別在於方法2更接近底層。
* 1.讓View支持wrap_content*
因為直接繼承View或者ViewGroup的控件,如果不在onMeasure中對wrap_content做特殊處理,當外界在布局中使用wrap_content時就無法達到預期的效果。
* 2.如果有必要,讓你的View支持padding*
因為直接繼承View的控件,如果不在draw方法中處理padding,那麼padding屬性無法起作用的。另外,直接繼承自ViewGroup的控件需要在onMeasure和onLayout中考慮padding和子元素的margin對其造成的影響不然將導致padding和子元素的margin失效。
* 3.盡量不要在View在中使用Handler,沒必要*
View內部本身就提供了post系列的方法,完全可以替代Handler的作用,當然除非你明確地要使用Handler來發送消息。
* 4.View中如果有線程或者動畫,需要及時停止,參考View#onDetachedFromWinow*
如果線程或者動畫需要停止時,onDetachedFromWindow是一個很好的時機。當包含此View的Activity退出或者當前View被remove時,View的onDetachedFromWindow方法會被調用,此方法對應的是onAttachedToWindow,當包含此View的Activity啟動時,View的onAttachedToWindow方法會被調用。同時,當View變得不可見時,我們也需要停止線程和動畫,如果不及時處理這種問題,可能會造成內存洩露。
* 5.View帶有滑動嵌套情形時,需要處理好滑動沖突*
package view;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.View;
import com.superdaxue.customviewdemo.R;
/**
* Created by ZX_CC on 2016/4/26.
*/
public class CircleView extends View {
//WRAP_CONTENT 下的默認高度
private int mWidth = 100;
private int mHeight = 100;
private int mColor = Color.RED;
private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
public CircleView(Context context) {
super(context);
init();
}
public CircleView(Context context, AttributeSet attrs) {
this(context, attrs,0);
}
public CircleView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CircleView);
mColor = a.getColor(R.styleable.CircleView_circle_color,Color.RED);
a.recycle();
init();
}
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);
if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST){
setMeasuredDimension(mWidth,mHeight);
}else if (widthSpecMode == MeasureSpec.AT_MOST){
setMeasuredDimension(mWidth,heightSpecSize);
}else if (heightSpecMode == MeasureSpec.AT_MOST){
setMeasuredDimension(widthSpecSize,mHeight);
}
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
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);
}
}
package view;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Scroller;
/**
* Created by ZX_CC on 2016/4/26.
*/
public class HorizontalScrollViewEx extends ViewGroup {
private static final String TAG = "HorizontalScrollViewEx";
private int mChildrenSize ;
private int mChildrenWidth ;
private int mChildrenIndex ;
private Scroller mScroller ;
private VelocityTracker mVelocityTracker ;
//分別記錄上次滑動坐標
private int mLastX = 0;
private int mLastY = 0;
//分別記錄上次滑動的坐標(在onInterceptTouchEvent)
private int mLastXIntercept = 0;
private int mLastYIntercept = 0;
public HorizontalScrollViewEx(Context context) {
super(context);
init();
}
public HorizontalScrollViewEx(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public HorizontalScrollViewEx(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
if (mScroller == null){
mScroller = new Scroller(getContext());
mVelocityTracker = VelocityTracker.obtain();
}
}
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
boolean intercepted = false;
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
intercepted = false;
if ( !mScroller.isFinished() ){
mScroller.abortAnimation();
intercepted = true;
}
break;
case MotionEvent.ACTION_MOVE:
int deltaX = x - mLastXIntercept;
int deltaY = y - mLastYIntercept;
if (Math.abs(deltaX) > Math.abs(deltaY)){
intercepted = true;
}else{
intercepted = false;
}
break;
case MotionEvent.ACTION_UP:
intercepted = false;
break;
}
Log.e(TAG,"--intercepted = " + intercepted);
mLastX = x;
mLastY = y;
mLastXIntercept = x ;
mLastYIntercept = y ;
return intercepted;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
mVelocityTracker.addMovement(event);
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
if ( !mScroller.isFinished()){
mScroller.abortAnimation();
}
break;
case MotionEvent.ACTION_MOVE:
int deltaX = x - mLastX;
int deltaY = y - mLastY;
scrollBy( - deltaX ,0);
break;
case MotionEvent.ACTION_UP :
int scrollX = getScrollX();
mVelocityTracker.computeCurrentVelocity(1000);
float xVelocity = mVelocityTracker.getXVelocity();
if (Math.abs(xVelocity) >= 50){
mChildrenIndex = xVelocity > 0 ? mChildrenIndex -1 : mChildrenIndex + 1;
}else {
mChildrenIndex = (scrollX + mChildrenWidth/2) /mChildrenWidth;
}
mChildrenIndex = Math.max(0,Math.min(mChildrenIndex,mChildrenIndex-1));
int dx = mChildrenIndex * mChildrenWidth - scrollX ;
smoothScrollBy(dx,0);
mVelocityTracker.clear();
break;
}
mLastX = x;
mLastY = y;
return true;
}
@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 widthSpaceSize = MeasureSpec.getSize(widthMeasureSpec);
int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
int heightSpaceSize = MeasureSpec.getSize(heightMeasureSpec);
int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
if (childCount == 0){
setMeasuredDimension(0,0);
}else if(widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST){
final View childView = getChildAt(0);
measureWidth = childView.getMeasuredWidth() * childCount;
measureHeight = childView.getMeasuredHeight();
setMeasuredDimension(measureWidth,measureHeight);
}else if (heightSpecMode == MeasureSpec.AT_MOST){
final View childView = getChildAt(0);
measureHeight = childView.getMeasuredHeight();
setMeasuredDimension(widthSpaceSize,measureHeight);
}else if (widthSpecMode == MeasureSpec.AT_MOST){
final View childView = getChildAt(0);
measureWidth = childView.getMeasuredWidth() * childCount;
setMeasuredDimension(measureWidth,heightSpaceSize);
}
}
// @Override
// protected void onDraw(Canvas canvas) {
// super.onDraw(canvas);
// final int childCount = getChildCount();
//
// }
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int childLeft = 0;
final int childCount = getChildCount();
mChildrenSize = childCount;
for (int i = 0; i < childCount; i ++){
final View childView = getChildAt(i);
if (childView.getVisibility() != View.GONE){
final int childWidth = childView.getMeasuredWidth();
mChildrenWidth = childWidth;
//MarginLayoutParams lm = (MarginLayoutParams) childView.getLayoutParams();
// int m = min(lm.leftMargin,lm.rightMargin,lm.topMargin,lm.bottomMargin);
childView.layout(childLeft,0,childLeft + childWidth ,childView.getMeasuredHeight());
childLeft += childWidth;
}
}
}
private int min(int...params){
int min = params[0];
for (int p : params){
if (p < min){
min = p;
}
}
return min;
}
private void smoothScrollBy(int dx, int dy){
mScroller.startScroll(getScrollX(),0,dx,0,500);
invalidate();
}
@Override
public void computeScroll(){
if (mScroller.computeScrollOffset()){
scrollTo(mScroller.getCurrX(),mScroller.getCurrY());
postInvalidate();
}
}
@Override
protected void onDetachedFromWindow() {
mVelocityTracker.recycle();
super.onDetachedFromWindow();
}
}
本文實例講述了Android編程自定義Notification的用法。分享給大家供大家參考,具體如下:Notification是一種讓你的應用程序在不使用Activity
二維碼掃描 Android Zxing圖片拉伸解決 Zxing是google提供的二維碼掃描工程 默認是橫屏的 轉換成豎屏後圖片出現拉伸 這裡提供解決辦法: Zxi
本文為大家分享了兩款選擇器,一款可以針對時間進行選擇、一款可以針對日期進行選擇,供大家參考,具體內容如下一、時間選擇器1.1.布局<?xml version
Android之屏幕適配Android之屏幕適配 適配方式一圖片適配 適配方式二dimensxml文件適配 適配方式三布局文件適配 適配方式四java代碼適配 適配方式五