一、自定義View的分類
二、自定義View的須知
三、自定義View的實例
直接繼承View
這個view處理了一些常見的問題,是一個比較規範的示例。
支持xml配置wrap_parent
支持padding
支持xml自定義屬性
View的代碼:
package com.example.custom;
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.util.Log;
import android.view.View;
import com.example.testgesturedetector.R;
public class CircleView extends View {
private static final String TAG = "CircleView";
//顏色
private int mColor = Color.RED;
//畫筆樣式
private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
public CircleView(Context context) {
super(context);
init();
Log.d(TAG,"one");
}
//實際測試是調用到這個構造
public CircleView(Context context, AttributeSet attrs) {
super(context, attrs);
Log.d(TAG,"two");
TypedArray type = context.obtainStyledAttributes(attrs, R.styleable.CircleView);
//沒有指定顏色的話默認紅色
mColor = type.getColor(R.styleable.CircleView_circle_color, Color.RED);
type.recycle();
init();
}
//這裏處理自定義的屬性
public CircleView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
Log.d(TAG,"mColor = " + mColor);
TypedArray type = context.obtainStyledAttributes(attrs, R.styleable.CircleView);
//沒有指定顏色的話默認紅色
mColor = type.getColor(R.styleable.CircleView_circle_color, Color.RED);
type.recycle();
init();
}
//初始化
private void init() {
//設置顏色
mPaint.setColor(mColor);
}
//解決wrap_parent不生效問題,設置一個默認的寬高200
@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);
Log.d(TAG,"widthSpecSize = " + widthSpecSize + " || heightSpecSize = " + heightSpecSize);
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 onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
Log.d(TAG,"changed=" + changed + " || left=" + left + " || top=" + top + " || right=" + right + " || bottom=" + bottom);
}
@Override
protected void onDraw(Canvas canvas) {
Log.d(TAG,"onDraw");
//優化了padding的代碼
super.onDraw(canvas);
//padding值
int left = getPaddingLeft();
int right = getPaddingRight();
int top = getPaddingTop();
int bottom = getPaddingBottom();
//View的寬
int width = getWidth() - left - right;
//View的高
int height = getHeight() - top - bottom;
//圓的半徑 = 寬和高比較出的數 / 2
int radiu = Math.min(width, height) / 2;
//繪製圓
canvas.drawCircle(left + width / 2, top + height / 2, radiu, mPaint);
}
}
注意:mColor = type.getColor(R.styleable.CircleView_circle_color, Color.RED); 這個裏面的CircleView_circle_color(是declare-styleable 和 attr)
測試的activity的xml文件:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.example.custom.CircleView
android:id="@+id/cv"
android:layout_width="wrap_content"
android:layout_height="100dp"
android:background="#000000"
android:layout_margin="20dp"
android:padding="20dp"//支持padding
app:circle_color="#00ff00"//自定義屬性
/>
</LinearLayout>
自定義屬性,在res/values 裏面創建atts_custom_view.xml屬性配置文件(文件名稱可以自己配置)
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="CircleView">
<attr name="circle_color" format="color" />
</declare-styleable>
</resources>
實際真機運行OK,效果如下:
logcat打印如下:
繼承ViewGroup派生出來的Layout
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.getMode(widthMeasureSpec);
int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSpecSize = 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 (widthSpecMode == MeasureSpec.AT_MOST) {
final View childView = getChildAt(0);
measureWidth = childView.getMeasuredWidth() * childCount;
setMeasuredDimension(measureWidth, heightSpecSize);
} else if (heightSpecMode == MeasureSpec.AT_MOST) {
final View childView = getChildAt(0);
measureHeight = childView.getMeasuredHeight();
setMeasuredDimension(widthSpecSize, measureHeight);
}
}
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int childLeft = 0;
final int childCount = getChildCount();
mChildSize = childCount;
for (int i = 0; i < childCount; i++) {
final View childView = getChildAt(i);
final int childWidth = childView.getMeasuredWidth();
mChildWidth = childWidth;
childView.layout(childLeft, 0, childLeft + childWidth, childView.getMeasuredHeight());
childLeft += childWidth;
}
}
public class HorizontalScrollViewEx extends ViewGroup {
private int mChildrenSize;
private int mChildWidth;
private int mChildIndex;
//分別記錄上次滑動的座標
private int mLastX = 0;
private int mLastY = 0;
//分別記錄上次滑動的座標
private int mLastXIntercept = 0;
private int mLastYIntercept = 0;
private Scroller mScroller;
private VelocityTracker mVelocityTracker;
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 ev) {
boolean intercepted = false;
int x = (int) ev.getX();
int y = (int) ev.getY();
switch (ev.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;
}
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 scrollX = getScrollX();
mVelocityTracker.computeCurrentVelocity(1000);
float xVelocity = mVelocityTracker.getXVelocity();
if (Math.abs(xVelocity) >= 50) {
mChildIndex = xVelocity > 0 ? mChildIndex - 1 : mChildIndex + 1;
} else {
mChildIndex = (scrollX + mChildWidth / 2) / mChildWidth;
}
mChildIndex = Math.max(0, Math.min(mChildIndex, mChildrenSize - 1));
int dx = mChildIndex * mChildWidth - 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 widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSpecSize = MeasureSpec.getMode(widthMeasureSpec);
int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSpecSize = 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 (widthSpecMode == MeasureSpec.AT_MOST) {
final View childView = getChildAt(0);
measureWidth = childView.getMeasuredWidth() * childCount;
setMeasuredDimension(measureWidth, heightSpecSize);
} else if (heightSpecMode == MeasureSpec.AT_MOST) {
final View childView = getChildAt(0);
measureHeight = childView.getMeasuredHeight();
setMeasuredDimension(widthSpecSize, measureHeight);
}
}
@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);
final int childWidth = childView.getMeasuredWidth();
mChildWidth = childWidth;
childView.layout(childLeft, 0, childLeft + childWidth, childView.getMeasuredHeight());
childLeft += childWidth;
}
}
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();
}
}
下面給寫常用的開源的自定義view網址:
https://github.com/Trinea/android-open-project/
https://github.com/lightSky/Awesome-MaterialDesign
到此爲止,想必對view有了比較全面的認識。