1.什麼是View
View是Android系統中所有控件的基類,不管是TextView,還是Button,甚至是LinearLayout和RelativeLayout都有共同的基類View。除了View還有ViewGroup,ViewGroup翻譯成中就是控件組,顧名思義,他是一組控件。也就是一組View。
Activity中onCreate()方法中的setContentView()到底幹了啥
Android的每個控件都會在界面裏面佔據一塊矩形區域,在Android裏面控件大致被分爲兩類,一類是View,一類是ViewGroup。ViewGroup作爲父控件可以包含很多View控件,並且管理View。通常情況下,Activity中使用setContentView()方法來設置一個佈局。
根據上圖我們可以看出每個Activity中都包含一個Window對象,通常Window由PhoneWindow來實現了,PhoneWindow將一個DecorView設置成整個窗口的根View,DecorView封裝了窗口的一些常用方法,在顯示上它將屏幕分爲兩個部分,一個是TitleView,一個ContentView,所謂的ContentView是一個ID爲content的FrameLayout,也就是說Android的根佈局是一個FrameLayout,我們可以通過DDMS的Dump來分析Xml
看到上面的分析結果,證明我沒騙你吧。
根據上圖我們可以看到黃色的FrameLayout和綠色的FrameLayout的父佈局就是DecorView,其中黃色的FrameLayout是設置TitleView也就是ActionBar,綠色FrameLayout是設置內容的。這就是我們在去掉標題欄的方法要在setContnetView之前調用的原因。去標題欄的方法如下
requestWindowFeature(Window.FEATURE_NO_TITLE)
什麼是MotionEvent
手指在觸摸屏幕產生一系列的事件,都是通過MotionEvent對象來傳遞的,包括觸摸時候和移動時候的座標
- ACTION_DOWN 手指剛剛接觸屏幕
- ACTION_MOVE 手指在屏幕上滑動
- ACYION_UP 手指離開屏幕
通過MotionEvent對象我們可以拿到事件發生時的座標,系統提供了兩組獲取方式getX/getY和getRawX/getRawY,他們的區別很簡單,getX/getY返回的是當前View左上角的x和y座標getRawX和getRawY,是返回當前屏幕左上角的x和y軸座標。
什麼是TouchSlop
是系統能夠識別最小的滑動距離,最小滑動距離是個常量,和設備有關,獲取方式
ViewConfiguration.get(this).getScaledTouchSlop();
View的位置參數
View位置主要有四個頂點來決定的,它的的四個屬性分別是: left top rght buttom
根據上述關係圖,就能很快的計算出View的寬高
width=right-left;
height=buttom-top;
什麼是VelocityTracker
速度追蹤,用於追蹤手指在滑動過程中的速度,包含水平方向和垂直方向。使用方法如下,需要在onTouchEvent方法中調用
VelocityTracker velocityTracker=VelocityTracker.obtain();
velocityTracker.addMovement(event);
當前追蹤了當前速度,就可以使用下面方法來獲取當前速度
velocityTracker.computeCurrentVelocity(1000);
int x= (int) velocityTracker.getXVelocity();
int y= (int) velocityTracker.getYVelocity();
獲取水平方向和垂直方向的速度前一定要調用computeCurrentVelocity(1000); 指的是一秒內滑動距離(單位時間內滑動的距離就是速度)
當不需要的時候需要回收(一般在ACTION_UP的時候就需要回收了)
velocityTracker.clear();
velocityTracker.recycle();
View的測量
在一些娛樂性節目中,常常出現了一個人描述物體,另一個人畫出該物體。那麼系統是如何畫出View的,首先我們得告訴它View的規格吧。在View繪製之前,必須進行測量。Android提供了一個功能強大的輔助類MeasureSpec,通過它能幫助我們完成View的測量
測量有三種模式
EXACTLY
即精準的測量模式,當控件的layout_width和layout_height爲具體值時(layout_width=”100dp”),系統使用就是這個精準模式
AT_MOST
即最大值模式,當控件的layout_width=”wrap_content”,控件大小一般隨着子控件的大小變化而變化,此時控件大小隻要不超過父控件即可。
UNSPECIFIED
這個屬性一般在繪製自定義View的時候使用
View默認的onMeasure()方法只支持EXACTLY,如果想讓你的控件支持warp_content屬性,就必須重寫onMeasure()方法,
模版代碼,當specMode爲EXACTLY模式,直接使用specSize的值,當爲其他兩種模式時候,需要給他一個默認大小的值。如果爲warp_content屬性,即AT_MOST模式需要取出我們指定大小和specSize中最小的值作爲測量值
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = measure(widthMeasureSpec);
int height = measure(heightMeasureSpec);
setMeasuredDimension(width, height);
}
public int measure(int measureSpec) {
int result = 0;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
if (specMode == MeasureSpec.EXACTLY) {
result = specSize;
} else {
result = 200; //默認值
if (specMode == MeasureSpec.AT_MOST) {
result = Math.min(result, specSize);
}
}
return result;
}
View的繪製
當測量好了一個View之後,就可以通過重寫onDraw()方法,並使用Canvas對象來繪製圖形。
- 畫圓
paint.setColor(Color.GREEN);
paint.setStyle(Paint.Style.FILL);
//圓心x和y軸座標,圓半徑
canvas.drawCircle(getWidth() / 2, getHeight() / 2, 200, paint);
paint.setColor(Color.RED);
li(canvas, 1, getWidth() / 2, getHeight() / 2);
- 畫線
paint.setColor(Color.BLACK);
paint.setStrokeWidth(10);
paint.setStyle(Paint.Style.FILL);
//開始的x和y座標,結束的x和y座標
canvas.drawLine(100, 100, 400, 100, paint);
//該線的起點是100,終點爲400,中心點的位置是 (stopX-startX)/2+距離左邊的距離=中心點位置
li(canvas, 2, (400 - 100) / 2 + 100, 100);
- 畫點
paint.setStrokeWidth(50);
paint.setColor(Color.YELLOW);
paint.setStyle(Paint.Style.FILL);
//點的x和y軸座標
canvas.drawPoint(200, 200, paint);
li(canvas, 3, 200, 200);
- 畫矩形
paint.setColor(Color.BLUE);
paint.setStrokeWidth(2);
paint.setStyle(Paint.Style.FILL);
Rect rect = new Rect(500, 100, 800, 400);
canvas.drawRect(rect, paint);
li(canvas, 4, (800 - 500) / 2 + 500, (400 - 100) / 2 + 100);
- 畫圓角矩形
paint.setColor(Color.DKGRAY);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(10);
RectF rectF = new RectF(100, 400, 400, 600);
//矩形的位置,矩形的圓角大小
canvas.drawRoundRect(rectF, 10, 30, paint);
li(canvas, 5, (400 - 100) / 2 + 100, (600 - 400) / 2 + 400);
- 畫橢圓
RectF r1 = new RectF(100, 800, 300, 1100);
paint.setColor(Color.GRAY);
canvas.drawOval(r1, paint);
li(canvas, 6, (300 - 100) / 2 + 100, (1100 - 800) / 2 + 800);
- 畫圓弧
paint.setColor(Color.LTGRAY);
paint.setStrokeWidth(10);
paint.setStyle(Paint.Style.STROKE);
RectF r2 = new RectF(100, 1200, 400, 1500);
//圓弧的位置,弧度的起始角度,結束角度,是否封口,畫筆
canvas.drawArc(r2, 0, 180, false, paint);
li(canvas, 7, (400 - 100) / 2 + 100, (1500 - 1200) / 2 + 1200);
paint.setStrokeWidth(2);
paint.setStyle(Paint.Style.STROKE);
RectF r3 = new RectF(300, 1000, 600, 1300);
canvas.drawArc(r3, 0, 180, true, paint);
li(canvas, 8, (600 - 300) / 2 + 300, (1300 - 1000) / 2 + 1000);
具體效果看這兒
這裏貼上關於文字的繪製代碼
public void li(Canvas canvas, int i, int x, int y) {
paint.setTextSize(50);
float textWidth = paint.measureText(String.valueOf(i)) / 2;
float textHeight = (paint.descent() + paint.ascent()) / 2;
paint.setStrokeWidth(1);
paint.setStyle(Paint.Style.FILL);
paint.setColor(Color.RED);
canvas.drawText(String.valueOf(i), x - textWidth, y - textHeight, paint);
}
關於View繪製的實際案例
代碼如下交替圓環
/**
* Created by xiongchengguang on 2016/7/9.
*/
public class CircleView extends View {
private static final int DEFAULT_FIRSTCOLOR = 0xff12ffee;
private static final int DEFAULT_SECONCOLOR = 0xffeeff0e;
private static final int DEFAULT_WIDTH = 50;
private static final int DEFAULT_SPEED = 20;
private int firstColor;
private int seconColor;
private int width;
private int speed;
private Paint paint = new Paint();
private int progress = 10;
public CircleView(Context context, AttributeSet attrs) {
super(context, attrs);
init(attrs);
}
public void init(AttributeSet attrs) {
TypedArray typedArray = getContext().obtainStyledAttributes(attrs, R.styleable.CircleView);
firstColor = typedArray.getColor(R.styleable.CircleView_firstColor, DEFAULT_FIRSTCOLOR);
seconColor = typedArray.getColor(R.styleable.CircleView_secondColor, DEFAULT_SECONCOLOR);
width = (int) typedArray.getDimension(R.styleable.CircleView_circleWidth, px2dp(DEFAULT_WIDTH));
speed = typedArray.getInt(R.styleable.CircleView_speed, DEFAULT_SPEED);
typedArray.recycle();
new Thread(new Runnable() {
public void run() {
while (true) {
progress++;
if (progress == 360) {
progress = 0;
}
postInvalidate();
SystemClock.sleep(speed);
}
}
}).start();
}
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int centent = getWidth() / 2;
int radius = centent - width / 2;
paint.setStrokeWidth(width);
paint.setAntiAlias(true);
paint.setStyle(Paint.Style.STROKE);
RectF rectF = new RectF(centent - radius, centent - radius, centent + radius, centent + radius);
paint.setColor(firstColor);
canvas.drawCircle(centent, centent, radius, paint);
paint.setColor(seconColor);
canvas.drawArc(rectF, 0, progress, false, paint);
paint.setStrokeWidth(1);
paint.setColor(Color.LTGRAY);
paint.setTextSize(20);
canvas.drawRect(rectF, paint);
String text = ((int) 100 / 360) + progress + "%";
float textWidth = paint.measureText(text) / 2;
float textHeight = (paint.descent() + paint.ascent()) / 2;
canvas.drawText(text, centent - textWidth, centent - textHeight, paint);
}
public int px2dp(int val) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, val, getResources().getDisplayMetrics());
}
public int px2sp(int val) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, val, getResources().getDisplayMetrics());
}
}
波紋效果
/**
* Created by xiongchengguang on 2016/7/9.
*/
public class MoireView extends View {
private int mMaxRadius;
private int speed = 200;
private long duration = 2000;
private Paint paint;
private List<Circle> arr = new ArrayList<>();
private float x, y;
public MoireView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
private void init() {
paint = new Paint();
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(2);
paint.setColor(Color.LTGRAY);
paint.setAntiAlias(true);
new Thread(new Runnable() {
public void run() {
while (true) {
newCircle();
SystemClock.sleep(speed);
}
}
}).start();
}
@Override
public boolean onTouchEvent(MotionEvent event) {
x = event.getRawX();
y = event.getRawY();
return super.onTouchEvent(event);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Iterator<Circle> iterator = arr.iterator();
while (iterator.hasNext()) {
try {
Circle circle = iterator.next();
if (System.currentTimeMillis() - circle.currentTimeMillis < duration) {
paint.setAlpha(circle.getAlpha());
canvas.drawCircle(x, y, circle.getRadius(), paint);
} else {
iterator.remove();
}
} catch (Exception e) {
e.printStackTrace();
break;
}
}
invalidate();
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mMaxRadius = Math.min(w, h);
}
public void newCircle() {
Circle circle = new Circle();
arr.add(circle);
}
class Circle {
private long currentTimeMillis;
public Circle() {
currentTimeMillis = System.currentTimeMillis();
}
public int getAlpha() {
float per = (System.currentTimeMillis() - currentTimeMillis) / duration;
return (int) ((1F - per) * 255);
}
public float getRadius() {
float per = (System.currentTimeMillis() - currentTimeMillis) * 1.0f / duration;
return per * mMaxRadius;
}
}
}