51.自定义布局-SlidingLayout

涉及知识点:
1.View绘制三部曲(onMeasure、onLayout、Draw)(第一步和第三步本控件未做处理)
2.ScrollBy相对滚动、ScrollTo绝对滚动、Scroller滚动器的应用
3.TouchEvent处理三部曲(dispatch分发、intercept拦截、onTouchEvent处理)(第一步本控件未做处理)
4.自定义回调接口

效果图:


控件源码:
public class SlidingLayout extends RelativeLayout {
private Scroller scroller;//滚动器

/**
* 构造器
*
* @param context
* @param attrs
*/
public SlidingLayout(Context context, AttributeSet attrs) {
super(context, attrs);
mLog.setLogFlag(false);
initView();
}

/**
* 初始化视图
*/
private void initView() {
scroller = new Scroller(getContext(), new DecelerateInterpolator());
}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
screenWidth = getMeasuredWidth();
}

List<Integer> childLeft;//记录每个子视图的左边界

@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
childLeft = new ArrayList<Integer>();

int left = 0;
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
childLeft.add(left);
child.layout(left, 0, left + child.getMeasuredWidth(), child.getMeasuredHeight());
left += child.getMeasuredWidth();//宽度累加
}
}

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
return super.dispatchTouchEvent(ev);
}

float downX, downY, moveX, moveY, upX, upY;
int disX, disY;//绝对偏移值(move相对于down

float lastX, lastY;
int disOffX, disOffY;//相对偏移值(move相对于上一次move)


@Override
public boolean onInterceptTouchEvent(MotionEvent event) {

switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
downX = event.getX();
downY = event.getY();
mLog.e("ACTION_DOWN:downX=" + downX + " downY=" + downY);
doDown();
break;

case MotionEvent.ACTION_MOVE:
moveX = event.getX();
moveY = event.getY();
disX = (int) (moveX - downX);
disY = (int) (moveY - downY);
//如果是左右滑动就拦截
if (Math.abs(disX) > Math.abs(disY)) {
return true;
}
}
return super.onInterceptTouchEvent(event);
}


@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
downX = event.getX();
downY = event.getY();
mLog.e("ACTION_DOWN:downX=" + downX + " downY=" + downY);
doDown();
break;

case MotionEvent.ACTION_MOVE:
moveX = event.getX();
moveY = event.getY();
disX = (int) (moveX - downX);
disY = (int) (moveY - downY);

if (lastX != 0 && lastY != 0) {
disOffX = (int) (moveX - lastX);
disOffY = (int) (moveY - lastY);
}
lastX = moveX;
lastY = moveY;
mLog.e("ACTION_MOVE:moveX=" + moveX + " moveY=" + moveY + " disX=" + disX + " disY=" + disY + " disOffX=" + disOffX + " disOffY=" + disOffY);
doMove();
break;

case MotionEvent.ACTION_UP:
upX = event.getX();
upY = event.getY();
disX = (int) (upX - downX);
disY = (int) (upY - downY);
lastX = 0;
lastY = 0;
mLog.e("ACTION_UP:upX=" + upX + " upY=" + upY + " disX=" + disX + " disY=" + disY);
doUp();
break;

}

return true;
}

private void doDown() {
}

int screenIndex;//记录当前是哪一个子视图

private void doMove() {
mLog.e("doMovedisOffX=" + disOffX + " getScrollX=" + getScrollX());


//如果是第一屏,并且偏移值向右,就什么都不做
if (screenIndex == 0 && disOffX - getScrollX() > 0) {

return;
}
//如果是最后一屏,并且偏移值向左,就什么都不做
if (screenIndex == getChildCount() - 1 && disOffX - getScrollX() < -childLeft.get(getChildCount() - 1)) {
return;
}
/**
* 特别注意:视图的滑动值和手势的偏移值是相反的!!!
*/
scrollBy(-disOffX, 0);
}

private void doUp() {
mLog.e("doUp:disOffX=" + disOffX + " getMeasuredWidth/2=" + getMeasuredWidth() / 2);

// //向右偏移==向左滑动,大于半屏时,跳到上一屏
if (disX >= getMeasuredWidth() / 2 && screenIndex > 0) {
mLog.e("跳到上一屏");
screenIndex--;
}
// //向左偏移==向右滑动,大于半屏时,跳到下一屏
else if (disX <= -getMeasuredWidth() / 2 && screenIndex < getChildCount() - 1) {
mLog.e("跳到下一屏");
screenIndex++;
}

//只有滑动过,才恢复屏幕(避免启动无用的scroller)
if (getScrollX() > 0) {
scrollerScreen();
}

}

int scrollTime = 500;

/**
* 使用scroller换屏(启动滑动动画,具体滑动需要到computeScroll里面执行)
*/
private void scrollerScreen() {
//启动滑动(默认时间为250ms,此处我自定义为1000ms
scroller.startScroll(getScrollX(), getScrollY(), childLeft.get(screenIndex) - getScrollX(), 0, scrollTime);
invalidate();
}


float screenIndexF;
int screenWidth;

/**
* 绝对滑动(相对于零点的滑动值)
*/
@Override
public void scrollTo(int x, int y) {
super.scrollTo(x, y);
mLog.e("scrollTox=" + x + " y=" + y);

}


/**
* 相对滑动(相对于上一位置的滑动值)
*/
@Override
public void scrollBy(int x, int y) {
super.scrollBy(x, y);
mLog.e("scrollByx=" + x + " y=" + y);


}

/**
* 计算滑动值(每一次重绘会自动调用此方法)
*/
@Override
public void computeScroll() {
super.computeScroll();
mLog.e("computeScroll");

//当滑动完成时,computeScrollOffset()返回false
if (scroller.computeScrollOffset()) {
scrollTo(scroller.getCurrX(), 0);
invalidate();
}

screenIndexF = getScrollX() / (float) screenWidth;
iScreenIndexListener.currentScreenIndex(screenIndexF);
mLog.e("screenIndexF=" + screenIndexF);
}

/**
* 自定义回调接口,回调当前的屏幕下标数
*/
interface IScreenIndexListener {
void currentScreenIndex(float screenIndexF);
}

IScreenIndexListener iScreenIndexListener;

public void setiScreenIndexListener(IScreenIndexListener iScreenIndexListener) {
this.iScreenIndexListener = iScreenIndexListener;
}
}

应用实例:
Java:
Xml:






原文地址:https://www.cnblogs.com/yutianran/p/5069680.html