介绍html
实现思路和代码java
布局文件android
下拉刷新实现ide
上拉加载实现布局
飞机转头和动画代码post
博文续篇动画
为以前的自定义View添加DrawerLayout(侧拉抽屉),为自定义View系列画上完美句号。spa
继续我上篇文章的内容:一步步实现ListView的Item侧滑删除菜单效果,仿QQ的聊天页面侧滑删除,这篇我将给ListView加上上拉刷新、下拉加载的动画效果。.net
其实,这篇内容和上篇内容用到的原理、逻辑、思路及实现等基本都相似。所谓一通百通啊,真的是这样,你只要掌握自定义View的一些套路,其实也不是很难嘛。code
主要解决问题(ListView 与下拉刷新、上拉加载的滑动冲突)
先来看看我实现的效果,首先是上拉刷新的效果:
那么看这样实现,若是你没作过的话,是否是以为这个很复杂呢?其实并否则。首先,依然是咱们的布局,布局分上、中、下三部分。上为上拉刷新内容、中为ListView、下为下拉加载内容。只要你清楚了这样的布局,那么实现起来轻轻松松啊,有没有?
看一下咱们的布局文件:
<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_orange_dark" android:gravity="center"> <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:gravity="center_horizontal" android:orientation="vertical"> <ProgressBar android:id="@+id/load_progress" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <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>
布局里的内容元素我就不作多的说明了,也没什么好说明的。咱们看最外层这个控件,是我自定义的继承FrameLayout的一个RefreshLayout类。为何用FrameLayout?我在上篇文章已经作了说明了,不清楚的依然能够在上面推荐连接点进去查看。首先,咱们将这三个家伙进行布局,固然是从上到下的那种。来看看代码:
@Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); mHeaderView.layout(0, -mHeaderHeight, mHeaderWidth, 0); mContentView.layout(0, 0, mContentWidth, mContentHeight); mFooterView.layout(0, mContentHeight, mFooterWidth, mContentHeight + mFooterHeight); }
这就完成了我从上至下的布局。既然,咱们把它布局到了屏幕上方,显然是看不见的。如今只能经过手指将它滑动下来显示,那么咱们在touch事件作滑动处理,来看看代码。
@Override public boolean onTouchEvent(MotionEvent event) { 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: if (isTop) { if (firstDownTag == 0) { /** * 若是是第一次的话,由于事件传递缘由 * onInterceptTouchEvent()执行了 ACTION_DOWN事件 * 标记了startY的值(这个值也许很是大,是根据手指按下的y坐标来定的) * 关键是onTouchEvent的ACTION_DOWN没法获得执行,因此 scrollTo(0, disY);将直接移动到startY的位置 * 效果就是致使第一次向下拉,瞬间移动了很是多 */ firstDownTag++; } else { final float dy = y - startY; int disY = (int) (getScrollY() - dy); if (-disY <= 0) { disY = 0; } if (-disY < mHeaderHeight) { scrollTo(0, disY); mRefreshProgress.setVisibility(INVISIBLE); if (-disY < mRefreshHeight) { tvRefreshText.setText("准备起飞"); startRefreshIcon(); } else { tvRefreshText.setText("加速中"); stopRefreshIcon(); } } } } startX = x; startY = y; break; case MotionEvent.ACTION_UP: isIntercept = false; if (-getScrollY() > mRefreshHeight) { startRefreshing(); } else { stopRefreshing(); } break; } return true; } @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 = x; upY = y; break; case MotionEvent.ACTION_MOVE: if (isTop) { if (upY - y < 0) { isIntercept = true; } else if (y - upY < 0) { isIntercept = false; } } break; case MotionEvent.ACTION_UP: upY = 0; upX = 0; break; } return isIntercept; }
这里有一个大坑咱们得爬,就是在RefreshLayout不拦截事件的时候,它默认会分发事件给ListView,致使ListView把touch事件给消费了,因此不拦截的状况下,尽管你怎么往下拉,它始终是拉不出来的。哈哈,那么解决方法就是咱们拦截它。可是拦截老是有条件的,这个条件有两点:
一、ListView的子项在第一个,也就是到达最顶部。
二、若是在ListView到达顶部前提下,手指还继续往下滑动,那么就是下拉刷新的动做了,在此时拦截它。
上面代码就是作了这两件事情,还有就是滑动动画。固然,这得在咱们RefreshLayout中实现对ListView的滑动监听的接口,判断是否处于顶部和底部:。还有一个就是咱们的飞机动画了,这比较简单了。
既然说完了下拉刷新,下面咱们来看看上拉加载动画吧。
其实,上拉加载只是和咱们的下拉刷新方向相反的。既然咱们已经实现了下拉刷新,那么上拉加载还不是手到擒来嘛。由于咱们前面已经处理过了事件冲突,因此能够一路向前,通畅无阻。
咱们看一下关键代码,最主要的仍是咱们的touch事件的代码,添加上拉加载的逻辑代码,其余都很是简单了:
@Override public boolean onTouchEvent(MotionEvent event) { 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: if (isTop) { if (firstDownTag == 0) { /** * 若是是第一次的话,由于事件传递缘由 * onInterceptTouchEvent()执行了 ACTION_DOWN事件 * 标记了startY的值(这个值也许很是大,是根据手指按下的y坐标来定的) * 关键是onTouchEvent的ACTION_DOWN没法获得执行,因此 scrollTo(0, disY);将直接移动到startY的位置 * 效果就是致使第一次向下拉,瞬间移动了很是多 */ firstDownTag++; } else { final float dy = y - startY; int disY = (int) (getScrollY() - dy); if (-disY <= 0) { disY = 0; } if (-disY < mHeaderHeight) { scrollTo(0, disY); mRefreshProgress.setVisibility(INVISIBLE); if (-disY < mRefreshHeight) { tvRefreshText.setText("准备起飞"); startRefreshIcon(); } else { tvRefreshText.setText("加速中"); stopRefreshIcon(); } } } } else if (isBottom) {/** 在ListView底部,继续上拉 **/ final float dy = y - startY; int disY = (int) (getScrollY() - dy); if (disY < 0) { disY = 0; ivLoadingIcon.setVisibility(VISIBLE); mLoadingProgress.setVisibility(INVISIBLE); } else if (disY >= mLoadingHeight) { disY = mLoadingHeight + 5; } scrollTo(getScrollX(), disY); // if (dy < 0) { // startLoadingIcon(); // } else { // stopLoadingIcon(); // } } startX = x; startY = y; break; case MotionEvent.ACTION_UP: isIntercept = false; if (isTop) { if (-getScrollY() > mRefreshHeight) { startRefreshing(); } else { stopRefreshing(); } } else if (isBottom) { if (getScrollY() > mLoadingHeight) { startLoading(); } else { stopLoading(); } } break; } return true; } @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) { isIntercept = true; } else if (y - upY < 0) { isIntercept = false; } } else if (isBottom) { /** 上拉加载拦截 **/ if (y - downY < 0) { isIntercept = true; } else if (y - downY > 0) { isIntercept = false; } } break; case MotionEvent.ACTION_UP: downX = upY = 0; downX = upX = 0; break; } return isIntercept; }
private void stopRefreshing() { mScroller.startScroll(getScrollX(), getScrollY(), 0, -getScrollY()); /** * ListView子项移动到第一个 */ mListView.setSelection(0); invalidate(); } private void startRefreshing() { mScroller.startScroll(getScrollX(), getScrollY(), 0, -mRefreshHeight - getScrollY()); tvRefreshText.setText("起飞咯~"); mRefreshProgress.setVisibility(VISIBLE); startIconAnimation(); invalidate(); /** * 模拟刷新完成,延迟关闭 */ handler.postDelayed(() -> stopRefreshing(), 2000); } private void startLoading() { mScroller.startScroll(getScrollX(), getScrollY(), 0, mFooterHeight - getScrollY()); ivLoadingIcon.setVisibility(INVISIBLE); mLoadingProgress.setVisibility(VISIBLE); invalidate(); handler.postDelayed(() -> stopLoading(), 1500); } private void stopLoading() { mScroller.startScroll(getScrollX(), getScrollY(), 0, -getScrollY(),1500); ivLoadingIcon.setVisibility(VISIBLE); mLoadingProgress.setVisibility(INVISIBLE); ivLoadingIcon.setPivotX(ivLoadingIcon.getWidth() / 2); ivLoadingIcon.setPivotY(ivLoadingIcon.getHeight() / 2); ivLoadingIcon.setRotation(180); invalidate(); } private void startIconAnimation() { TranslateAnimation animation = new TranslateAnimation(0, 0, getScaleY(), -mRefreshHeight); animation.setFillAfter(false); animation.setDuration(2000); ivRefreshIcon.startAnimation(animation); } private void startRefreshIcon() { ivRefreshIcon.setPivotX(ivRefreshIcon.getWidth() / 2); ivRefreshIcon.setPivotY(ivRefreshIcon.getHeight() / 2); ivRefreshIcon.setRotation(180); } private void stopRefreshIcon() { ivRefreshIcon.setPivotX(ivRefreshIcon.getWidth() / 2); ivRefreshIcon.setPivotY(ivRefreshIcon.getHeight() / 2); ivRefreshIcon.setRotation(360); }
那么,咱们整个下拉刷新、上拉加载的最终效果:
©原文连接:https://blog.csdn.net/smile_Running/article/details/81950872
@做者博客:_Xu2WeI
@更多博文:查看做者的更多博文