【声明】:未经许可,禁止转载!
· 续篇
继续上篇余下的内容:ListView添加下拉刷新、上拉加载,其实很简单
当你阅读这篇文章时,你可能会一头雾水。原因呢,因为这是我的一个ListView系列的文章,从零到一,全部是自定义View实现的,当然你也许说为什么不用第三方库。这就是差距了,虽然我们可能做不到第三方库严谨的逻辑、漂亮的UI特效,但是这是我们实实在在自己写的。在我写完这几篇文章之后,我发现我的自定义View的水平不断上升,从之前对它的惧怕、到懵懂、到简单的UI、再到现。可谓是一路的成长,在逻辑上严谨了许多。
今天,我们来为它实现一个侧滑抽屉的效果,比如我们最熟悉的QQ侧滑抽屉。当然,我的这一系列文章的UI其实也是随着QQ、以及大部分APP的潮流。那么看一下我们自定义侧滑菜单的效果图:
可能,我接连下来的几篇会越说越范,没有刚开始那么细。因为,你一旦理解了,自然就能明白我的表达和思路。不理解的话,哈哈,继续看我之前写的比较详细的吧。
首先,我们说一下这个抽屉的思路:
1、抽屉位置,肯定是左侧、或右侧的屏幕外的区域。那就得将它布局到屏幕外,算准它的坐标,然后通过手指滑动显示出来。
2、解决冲突问题,这里规定的是我的抽屉在左侧。
抽屉的状态有关、开
一、前提是在抽屉并没打开,判断手指确实是往右移动的动作,要打开抽屉的行为,这时候才可以移动抽屉。那么在这个时候,拦截touch事件,将抽屉移动显示出来。
二、抽屉打开了,这个时候要去判断抽屉内是否有滚动视图、和点击事件的传递行为。如果有,要分条件拦截;如果没有,全部拦截。
三、与下拉刷新的冲突,因为处在下拉刷新的行为,这时候我们不能够拉出抽屉,所以要剥夺父视图的touch事件。
首先来看看布局和布局的处理代码:
<listview.example.x.slidelistview.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="listview.example.x.slidelistview.MainActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginRight="72dp"
android:orientation="vertical">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="180dp">
<de.hdodenhof.circleimageview.CircleImageView
android:id="@+id/img"
android:layout_width="120dp"
android:layout_height="120dp"
android:layout_gravity="center"
android:src="@drawable/img_7" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|center_horizontal"
android:text="_Xu2WeI"
android:textSize="20sp" />
</FrameLayout>
<ListView
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
<listview.example.x.slidelistview.RefreshLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="180dp"
android:background="@android:color/holo_red_dark"
android:gravity="center_horizontal">
<ProgressBar
android:id="@+id/refresh_progress"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_gravity="bottom|right"
tools:ignore="RtlHardcoded" />
<TextView
android:id="@+id/tv_refresh_state"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|center_horizontal"
android:layout_marginBottom="32dp"
android:layout_marginTop="8dp"
android:textColor="@android:color/white" />
<ImageView
android:id="@+id/iv_refreshing"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_gravity="center_horizontal|bottom"
android:src="@drawable/ic_flight_black_24dp" />
</FrameLayout>
<ListView
android:id="@+id/lv_contact"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="80dp"
android:background="@android:color/holo_red_dark"
android:gravity="center">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:orientation="vertical">
<FrameLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/ivLoadingIcon"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_gravity="center_horizontal"
android:src="@drawable/ic_flight_black_24dp" />
<ProgressBar
android:id="@+id/load_progress"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</FrameLayout>
<TextView
android:id="@+id/tv_load_state"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="正在加载"
android:textColor="@android:color/white" />
</LinearLayout>
</RelativeLayout>
</listview.example.x.slidelistview.RefreshLayout>
</listview.example.x.slidelistview.DrawerLayout>
首先是拦截事件的处理代码
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
boolean intercept = false;
final float x = event.getX();
final float y = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
startX = x;
startY = y;
break;
case MotionEvent.ACTION_MOVE:
final float dx = x - startX;
final float dy = y - startX;
/**
* 判断抽屉未打开时
*/
if (!isDrawerOpen) {
/**
* 抽屉右滑
*/
if (x - startX > 0 && dx > 0) { // 右滑
intercept = true;
}
} else {
/**
* 抽屉打开,并非拦截所有事件,也许抽屉里还有滚动,这里要做出判断。
* 全拦截,则抽屉子菜单选项滚动不了
*/
final int disX = (int) Math.abs(x - startX);
final int disY = (int) Math.abs(y - startY);
if (disX > disY && disX > 15) {
intercept = true;
}
//如果抽屉里有滚动列表,则不拦截它
}
break;
case MotionEvent.ACTION_UP:
break;
}
return intercept;
}
接着是抽屉移动事件:
@Override
public boolean onTouchEvent(MotionEvent event) {
final float x = event.getX();
final float y = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
moveX = startX = x;
moveY = startY = y;
break;
case MotionEvent.ACTION_MOVE:
final float dx = x - startX;
final float dy = y - startY;
int disX = (int) (getScrollX() - dx);
scrollTo(disX, getScrollY());
if (x - startX < 0) {
isMoveLeft = true;
} else if (x - startX > 0) {
isMoveLeft = false;
}
startX = x;
startY = y;
break;
case MotionEvent.ACTION_UP:
/**
* 抽屉未打开
*/
if (!isDrawerOpen) {
if (-getScrollX() > mDrawerWidth / 3) {
openDrawer();
} else {
closeDrawer();
}
} else {
/**
* 抽屉是开着
*/
if (isMoveLeft) { /** 产生左移行为,关闭抽屉 **/
if (-getScrollX() > 15) { //至少滑动一点距离
closeDrawer();
}
} else {
/** 产生右移行为,恢复打开时状态 **/
openDrawer();
}
}
break;
}
return true;
}
还有就是在下拉刷新或者是上拉加载的时候,剥夺DrawerLayout的touch事件处理权。限制在下拉刷新或者是上拉加载的时候打开抽屉。是在之前的下拉刷新或者是上拉加载添加剥夺事件的代码(注意:此方法是RefreshLayout类中的中断事件代码,并非本类。):
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
final float x = ev.getX();
final float y = ev.getY();
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
isIntercept = false;
upX = downX = x;
upY = downY = y;
break;
case MotionEvent.ACTION_MOVE:
if (isTop) {
/** 下拉刷新拦截 **/
if (upY - y < 0) {
/**
* 在下拉刷新行为时,拦截Listview的滚动事件
*/
isIntercept = true;
/**
* 剥夺DrawerLayout的touch事件
*/
getParent().requestDisallowInterceptTouchEvent(true);
} else if (y - upY < 0) {
isIntercept = false;
}
} else if (isBottom) {
/** 上拉加载拦截 **/
if (y - downY < 0) {
/**
* 上拉加载时,拦截Listview的滚动事件
*/
isIntercept = true;
getParent().requestDisallowInterceptTouchEvent(true);
} else if (y - downY > 0) {
isIntercept = false;
}
}
break;
case MotionEvent.ACTION_UP:
downX = upY = 0;
downX = upX = 0;
break;
}
return isIntercept;
}
自定义DrawerLayout类的完整代码:
/**
* @Created by xww.
* @Creation time 2018/8/23.
*/
public class DrawerLayout extends FrameLayout {
private View mDrawerView;
private View mContentView;
private int mDrawerWidth;
private int mDrawerHeight;
private int mContentWidth;
private int mContentHeight;
private Scroller mScroller;
private float startX;
private float startY;
private float moveX;
private float moveY;
private boolean isDrawerOpen;
private boolean isMoveLeft;
private boolean isVerticalScroll;
public DrawerLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
mScroller = new Scroller(context);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mDrawerView = getChildAt(0);
mContentView = getChildAt(1);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
mDrawerWidth = mDrawerView.getMeasuredWidth();
mDrawerHeight = mDrawerView.getMeasuredHeight();
mContentWidth = mContentView.getMeasuredWidth();
mContentHeight = mContentView.getMeasuredHeight();
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
mDrawerView.layout(-mDrawerWidth, 0, 0, mDrawerHeight);
mContentView.layout(0, 0, mContentWidth, mContentHeight);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
final float x = event.getX();
final float y = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
moveX = startX = x;
moveY = startY = y;
break;
case MotionEvent.ACTION_MOVE:
final float dx = x - startX;
final float dy = y - startY;
int disX = (int) (getScrollX() - dx);
if (-disX >= mDrawerWidth) {
disX = -mDrawerWidth;
}
scrollTo(disX, getScrollY());
if (x - startX < 0) {
isMoveLeft = true;
} else if (x - startX > 0) {
isMoveLeft = false;
}
startX = x;
startY = y;
break;
case MotionEvent.ACTION_UP:
/**
* 抽屉未打开
*/
if (!isDrawerOpen) {
if (-getScrollX() > mDrawerWidth / 3) {
openDrawer();
} else {
closeDrawer();
}
} else {
/**
* 抽屉是开着
*/
if (isMoveLeft) { /** 产生左移行为,关闭抽屉 **/
if (-getScrollX() > 15) { //至少滑动一点距离
closeDrawer();
}
} else {
/** 产生右移行为,恢复打开时状态 **/
openDrawer();
}
}
break;
}
return true;
}
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
boolean intercept = false;
final float x = event.getX();
final float y = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
startX = x;
startY = y;
break;
case MotionEvent.ACTION_MOVE:
final float dx = x - startX;
final float dy = y - startX;
/**
* 判断抽屉未打开时
*/
if (!isDrawerOpen) {
/**
* 抽屉右滑
*/
if (x - startX > 0 && dx > 0) { // 右滑
intercept = true;
}
} else {
/**
* 抽屉打开,并非拦截所有事件,也许抽屉里还有滚动,这里要做出判断。
* 全拦截,则抽屉子菜单选项滚动不了
*/
final int disX = (int) Math.abs(x - startX);
final int disY = (int) Math.abs(y - startY);
if (disX > disY && disX > 15) {
intercept = true;
}
//如果抽屉里有滚动列表,则不拦截它
}
break;
case MotionEvent.ACTION_UP:
break;
}
return intercept;
}
private void openDrawer() {
isDrawerOpen = true;
mScroller.startScroll(getScrollX(), getScrollY(), -getScrollX() - mDrawerWidth, 0);
invalidate();
}
private void closeDrawer() {
isDrawerOpen = false;
mScroller.startScroll(getScrollX(), getScrollY(), -getScrollX(), 0);
invalidate();
}
@Override
public void computeScroll() {
super.computeScroll();
if (mScroller.computeScrollOffset()) {
scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
invalidate();
}
}
}
源码下载:
CSDN资源链接:自定义ViewGroup仿QQ侧拉删除、侧拉抽屉;下拉刷新、上拉加载动画
©版权所有:https://blog.csdn.net/smile_Running/article/details/81981965