1、实现基类
(1)item数量控制
private void ensureItems(int count) {
int c = getChildCount();
if (c < count) {
while (c++ < count) {
//添加View
View view = onCreateItemView();
addView(view);
}
} else if (c > count) {
while (c-- > count) {
//移除View
View view = getChildAt(count);
removeViewAt(count);
onDestroyItemView(view);
}
}
for (int i = 0; i < getChildCount(); i++) {
//初始化,并设置tag
View view = getChildAt(i);
view.setTag(KEY_INDEX, i);
onInitItemView(view);
}
}
(2)Mearsure处理
@Override
protected final void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (getChildCount() > 0) {
// base on width or height
boolean base = getMeasureBase();
int measureSpec = base ? heightMeasureSpec : widthMeasureSpec;
int mode = MeasureSpec.getMode(measureSpec);
int size = MeasureSpec.getSize(measureSpec);
switch (mode) {
// 精准尺寸
case MeasureSpec.EXACTLY:
//最大尺寸
case MeasureSpec.AT_MOST:
Rect bound = getBound(size, base, getChildCount());
widthMeasureSpec = MeasureSpec.makeMeasureSpec(bound.width(), mode);
heightMeasureSpec = MeasureSpec.makeMeasureSpec(bound.height(), mode);
break;
//未知尺寸
case MeasureSpec.UNSPECIFIED:
break;
}
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
(3)Layout处理
@Override
protected final void onLayout(boolean changed, int l, int t, int r, int b) {
if (getChildCount() > 0) {
Rect[] bounds = getItemBounds(r - l, b - t, getChildCount());
for (int i = 0; i < getChildCount(); i++) {
View view = getChildAt(i);
Rect bound = bounds[i];
view.layout(bound.left, bound.top, bound.right, bound.bottom);
}
}
}
(4)基类函数
//创建一个View
protected abstract View onCreateItemView();
//销毁一个View
protected abstract void onDestroyItemView(View view);
//初始化一个View
protected abstract void onInitItemView(View view);
//取出丈量基准
protected abstract boolean getMeasureBase();
//取出整个控件的大小
protected abstract Rect getBound(int size, boolean base, int count);
//获取单个子视图
protected abstract Rect[] getItemBounds(int width, int height, int count);
(5)自定义属性
xml文件
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="Sudoku">
<attr name="spacing" format="integer"/>
<attr name="item_background" format="reference" />
</declare-styleable>
</resources>
引用到布局文件
<com.netease.study.ui.sudoku.SudokuView
android:id="@+id/sodoku"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/colorBackground"
app:spacing="50">
</com.netease.study.ui.sudoku.SudokuView>
初始化属性
private void initAttrs(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.Sudoku);
spacing = ta.getInt(R.styleable.Sudoku_spacing, 0);
itemBackground = ta.getDrawable(R.styleable.Sudoku_item_background);
ta.recycle();
}
(6)定位
protected Rect getBound(int size, boolean base, int count) {
return new Rect(0, 0, size, size * count);
}
@Override
protected Rect[] getItemBounds(int width, int height, int count) {
Rect[] bounds = new Rect[count];
for (int i = 0; i < count; i++) {
int top = width * i;
bounds[i] = new Rect(spacing, top + spacing, width - spacing, top + width - spacing);
}
return bounds;
}
(7)手势探测
private void init(Context context) {
setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
boolean handled = gestureDetector.onTouchEvent(event);
return event.getAction() == MotionEvent.ACTION_DOWN || handled;
}
});
}
//手势动作
private final GestureDetector gestureDetector = new GestureDetector(null, new GestureDetector.SimpleOnGestureListener() {
//快速滑动
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
Toast.makeText(getContext(), "onFling", Toast.LENGTH_SHORT).show();
indexOffset += 1;
requestLayout();
return true;
}
//双击
@Override
public boolean onDoubleTap(MotionEvent e) {
Toast.makeText(getContext(), "onDoubleTap", Toast.LENGTH_SHORT).show();
doubleSpacing = !doubleSpacing;
requestLayout();
return true;
}
});