目前只能支持三张图片,支持横竖屏模式,手指滑动翻页到下一张卡片,手指点击也可以切换到当前卡片,并且选中的卡片会在整个ViewGroup的最上层,会被放大,可以自定义放大动画的时长。最基本的Android自定义控件,大神就别看了。
来先看效果图吧:
支持竖屏模式
也支持横屏模式:
主要是想熟悉一下自定义控件的基本测量和布局方式,其实使用LinearLayout或者是FrameLayout来做会更加方便,但是这个时候就不需要我们自己去重写onMeasure和onLayout方法了。
支持的自定义属性:
属性 | 描述 | 默认值 |
---|---|---|
scc_anim_duration | 卡片放大动画时间 | 300 |
scc_edge | 每个卡片顶边和底边的距离 | 60 |
scc_type | 竖屏还是横屏模式 | VERTICAL |
scc_min_change_distance | 手指最小滑动距离才会翻页 | 20 |
把ViewGroup中的三个View(可为任意的三个控件)按照预设好的边距和padding测量大小,然后三个view根据edge值来确定依次确定位置。我们没有用到canvas、path或者paint。没必要,我们只需要改变子View的绘制顺序,检测到用户的滑动或者是点击就invalidate重绘,把用户选中的view放在最后绘制这样就可以将当前选中的view放在最上层。这样放大选中的view就不会被遮住。
/**
* 获取子控件dispatchDraw的次序,将当前选中的View放在最后绘制
*/
@Override
protected int getChildDrawingOrder(int childCount, int i) {
//currentItemIndex 为当前选中的View在ViewGroup中的position
if (currentItemIndex < 0) {
return i;
}
if (i < (childCount - 1)) {
if (currentItemIndex == i)
i = childCount - 1;
} else {
if (currentItemIndex < childCount)
i = currentItemIndex;
}
return i;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// super.onMeasure(widthMeasureSpec, heightMeasureSpec);
/**
* 获得此ViewGroup上级容器为其推荐的宽和高,以及计算模式
*/
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int sizeWidth = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int sizeHeight = MeasureSpec.getSize(heightMeasureSpec);
/**
* 先测量整个Viewgroup的大小
*/
setMeasuredDimension(sizeWidth, sizeHeight);
int childCount = getChildCount();
int childWidth, childHeight;
/**由于每一个子View的宽高都是一样的所以就一起计算每一个View的宽高*/
if(ShapeType.VERTICAL.ordinal() == mShapeType){ //竖向模式
childWidth = getMeasuredWidth() - padding*2;
childHeight = getMeasuredHeight() - padding*2 - edge*2;
}else{ //横向模式
childWidth = getMeasuredWidth() - padding*2 - edge*2;
childHeight = getMeasuredHeight() - padding*2;
}
int childWidthMeasureSpec = 0;
int childHeightMeasureSpec = 0;
// 循环测量每一个View
for (int i = 0; i < childCount; i++) {
View childView = getChildAt(i);
// 系统自动测量子View:
// measureChild(childView, widthMeasureSpec, heightMeasureSpec);
/** 以一个精确值来测量子View的宽度 */
childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(childWidth, MeasureSpec.EXACTLY);
childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.EXACTLY);
childView.measure(childWidthMeasureSpec,childHeightMeasureSpec);
}
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int childCount = getChildCount();
// 循环测量每一个View
for (int i = 0; i < childCount; i++) {
View childView = getChildAt(i);
//四个方向的margin值
int measureL = 0, measurelT = 0, measurelR = 0, measurelB = 0;
if(ShapeType.VERTICAL.ordinal() == mShapeType){ //竖向模式
switch (i){
case 0:
measureL = padding;
measurelT = padding;
measurelB = childView.getMeasuredHeight() + padding;
measurelR = childView.getMeasuredWidth() + padding;
childView.layout(measureL, measurelT, measurelR, measurelB);
break;
case 1:
measureL = padding;
measurelT = padding + edge;
measurelB = childView.getMeasuredHeight() + padding + edge;
measurelR = childView.getMeasuredWidth() + padding;
childView.layout(measureL, measurelT, measurelR, measurelB);
break;
case 2:
measureL = padding;
measurelT = padding + edge*2;
measurelB = childView.getMeasuredHeight() + padding + edge*2;
measurelR = childView.getMeasuredWidth() + padding;
childView.layout(measureL, measurelT, measurelR, measurelB);
break;
}
}else{ //横向模式
switch (i){
case 0:
measureL = padding;
measurelT = padding;
measurelB = childView.getMeasuredHeight() + padding;
measurelR = childView.getMeasuredWidth() + padding;
childView.layout(measureL, measurelT, measurelR, measurelB);
break;
case 1:
measureL = padding + edge;
measurelT = padding;
measurelB = childView.getMeasuredHeight() + padding;
measurelR = childView.getMeasuredWidth() + padding + edge;
childView.layout(measureL, measurelT, measurelR, measurelB);
break;
case 2:
measureL = padding + edge*2;
measurelT = padding;
measurelB = childView.getMeasuredHeight() + padding;
measurelR = childView.getMeasuredWidth() + padding + edge*2;
childView.layout(measureL, measurelT, measurelR, measurelB);
break;
}
}
}
}
在手指滑动的时候为了防止频繁触发翻页,我使用了handler去发送翻页消息。
/**
* 事件分发
* onTouchEvent() 用于处理事件,返回值决定当前控件是否消费(consume)了这个事件
* @param event
* @return
*/
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.d("danxx", "onTouchEvent");
// return super.onTouchEvent(event);
/**以屏幕左上角为坐标原点计算的Y轴坐标**/
int y;
if(ShapeType.VERTICAL.ordinal() == mShapeType){ //竖屏模式取Y轴坐标
y = (int) event.getRawY();
}else{
y = (int) event.getRawX(); //横屏模式取X轴坐标
}
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.i(TAG, "MotionEvent.ACTION_DOWN");
// 手指按下时记录下y坐标
lastY = y;
break;
case MotionEvent.ACTION_MOVE:
Log.i(TAG, "MotionEvent.ACTION_MOVE");
// 手指向下滑动时 y坐标 = 屏幕左上角为坐标原点计算的Y轴坐标 - 手指滑动的Y轴坐标
int m = y - lastY;
if(m>0 && m>changeDistance){ //手指向下滑动 或者是左滑
changeHandler.removeMessages(MSG_UP);
changeHandler.sendEmptyMessageDelayed(MSG_UP, animDuration);
}else if(m< 0&& Math.abs(m)>changeDistance){ //手指向上滑动 或者右滑
changeHandler.removeMessages(MSG_DOWN);
changeHandler.sendEmptyMessageDelayed(MSG_DOWN, animDuration);
}
// 记录下此刻y坐标
this.lastY = y;
break;
case MotionEvent.ACTION_UP:
Log.i(TAG, "MotionEvent.ACTION_UP");
break;
}
return true;
}
/**
* 显示下面的一页
* 翻页成功返回true,否则false
*/
private boolean downPage(){
if(1 == currentItemIndex){
FocusAnimUtils.animItem(getChildAt(currentItemIndex), false, 1.0f, animDuration);
// 重绘,改变堆叠顺序
currentItemIndex = 2;
postInvalidate();
FocusAnimUtils.animItem(getChildAt(currentItemIndex), true, 1.06f, animDuration);
return true;
}else if(0 == currentItemIndex){
FocusAnimUtils.animItem(getChildAt(currentItemIndex), false, 1.0f, animDuration);
// 重绘,改变堆叠顺序
currentItemIndex = 1;
postInvalidate();
FocusAnimUtils.animItem(getChildAt(currentItemIndex), true, 1.06f, animDuration);
return true;
}else if(2 == currentItemIndex){
return false;
}
return false;
}
/**
* 显示上面的一页
* 翻页成功返回true,否则false
*/
private boolean upPage(){
if(1 == currentItemIndex){
FocusAnimUtils.animItem(getChildAt(currentItemIndex), false, 1.0f, animDuration);
// 重绘,改变堆叠顺序
currentItemIndex = 0;
postInvalidate();
FocusAnimUtils.animItem(getChildAt(currentItemIndex), true, 1.06f, animDuration);
return true;
}else if(0 == currentItemIndex){
return false;
}else if(2 == currentItemIndex){
FocusAnimUtils.animItem(getChildAt(currentItemIndex), false, 1.0f, animDuration);
currentItemIndex = 1;
postInvalidate();
FocusAnimUtils.animItem(getChildAt(currentItemIndex), true, 1.06f, animDuration);
return true;
}
return false;
}