项目中需要下拉刷新的功能,但是这个View不是ListView这类的控件,需要ViewGroup实现这个功能,一开始网上大略找了一下,没发现特别合适的,代码也是没怎么看懂,所以决定还是自己写一个。
于是翻出XlistView的源码看是一点一点看,再大致理解了XLisview源码,终于决定自己动手啦
为了省事,headView还是用了XListView的HeadView,省了很多事:)
下拉刷新,下拉刷新,肯定是先实现下拉功能,最开始我是打算通过 extends ScrollView 来实现,因为有现成的滚动效果嘛,可是实际因为两个原因放弃了:
1、ScrollView下只能有一个子控件View ,虽然在 Scroll下添加一个ViewGroup,然后讲headView动态添加进前面的ViewGroup,但是我还是比较习惯studio的可视化预览,总觉得不直观!
2、 ScrollView内嵌ListView时会发生 冲突,还需要去重写ListView。于是放弃换个思路!
关于上面的原因1:动态添加headView进ScrollView的中GroupView中,可以在重写ScrollView的onViewAdded()方法,将初始化时解析的headView添加进子GroupView
@Override
public void onViewAdded(View child) {
super.onViewAdded(child);
//因为headView要在最上面,最先想到的就是Vertical的LinearLayout
LinearLayout linearLayout = (LinearLayout) getChildAt(0);
linearLayout.addView(view, 0);
}
换个思路,通过extends LinearLayout来实现吧!
先做准备工作,我们需要一个HeaderView以及要获取到HeaderView的高度,还有初始时Layout的高度
private void initView(Context context) {
mHeaderView = new SRefreshHeader(context);
mHeaderViewContent = (RelativeLayout) mHeaderView.findViewById(R.id.slistview_header_content);
setOrientation(VERTICAL);
addView(mHeaderView, 0);
getHeaderViewHeight();
getViewHeight();
}
mHeaderView = new SRefreshHeader(context);
通过构造方法实例化HeaderView
mHeaderViewContent = (RelativeLayout)
mHeaderView.findViewById(R.id.slistview_header_content);
这是解析headerView内容区域iew,等会儿要获取这个view的高度,你肯定会问为啥不用上面的mHeaderView来获取高度,点进构造方法里可以看到如下代码
// 初始情况,设置下拉刷新view高度为0
LayoutParams lp = new LayoutParams(LayoutParams.MATCH_PARENT, 0);
mContainer = (LinearLayout) LayoutInflater.from(context).inflate(R.layout.listview_head_view_layout, null);
w(mContainer, lp);
如果直接获取mHeaderView的高度 那肯定是0
getHeaderViewHeight();
getViewHeight();
分别是获取HeaderView的高度和Layout的初始高度
/**
* 获取headView高度
*/
private void getHeaderViewHeight() {
ViewTreeObserver vto2 = mHeaderViewContent.getViewTreeObserver();
vto2.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
mHeaderViewHeight = mHeaderViewContent.getHeight();
mHeaderViewContent.getViewTreeObserver().removeGlobalOnLayoutListener(this);
}
});
}
/**
* 获取SRefreshLayout当前实例的高度
*/
private void getViewHeight() {
ViewTreeObserver thisView = getViewTreeObserver();
thisView.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
SRefreshLayout.this.mHeight = SRefreshLayout.this.getHeight();
SRefreshLayout.this.getViewTreeObserver().removeGlobalOnLayoutListener(this);
}
});
}
准备工作完成了,接下来就是要成下拉操作了
到这里,肯定一下就想到了onTouchEvent()方法,是的!现在就开始在这里施工
实现下拉一共 会经历三个过程
ACTION_UP→ACTION_MOVE→ACTION_UP
在ACTION_UP事件中,也就是手指按下的时候,我们需要做的只是记录按下时候的坐标
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
//记录起始高度
mLastY = ev.getRawY();//记录按下时的Y坐标
break;
然后就是ACTION_MOVE事件了,这里是最重要的,因为下拉时HeadView和Layout的高度变化都在这里进行
case MotionEvent.ACTION_MOVE:
if (!isRefreashing)
isRefreashing = true;
final float deltaY = ev.getRawY() - mLastY;
mLastY = ev.getRawY();
updateHeaderViewHeight(deltaY / 1.8f);//按一定比例缩小移动距离
updateHeight();
break;
里面的updateHeaderViewHeight和updateHeight分别是改变HeaderView的高度和Layout的高度
private void updateHeight() {
ViewGroup.LayoutParams lp = getLayoutParams();
//更新当前layout实例高度为headerView高度加上最初的layout高度
//如果不更新layout 会造成内容高度压缩 无法保持比例
lp.height = (mHeight + mHeaderView.getVisiableHeight());
setLayoutParams(lp);
}
private void updateHeaderViewHeight(float space) {
// if (space < 0)
// space = 0;
// int factHeight = (int) (space - mHeaderViewHeight);
if (mHeaderView.getStatus() != SRefreshHeader.STATE_REFRESHING) {
//如果不处于刷新中同时如果高度
if (mHeaderView.getVisiableHeight() < mHeaderViewHeight * 2 && mHeaderView.getStatus() != SRefreshHeader.STATE_NORMAL) {
mHeaderView.setState(SRefreshHeader.STATE_NORMAL);
}
if (mHeaderView.getVisiableHeight() > mHeaderViewHeight * 2 && mHeaderView.getStatus() != SRefreshHeader.STATE_READY) {
mHeaderView.setState(SRefreshHeader.STATE_READY);
}
}
mHeaderView.setVisiableHeight((int) space
+ mHeaderView.getVisiableHeight());
}
更新Header高度时,通过下拉的距离来判断是否到达刷新的距离,上面代码中我设定的是到达mHeaderView初始高度的两倍,就进入“释放刷新”的状态,如果没有达到则保持“下拉刷新”的状态
HeaderView中的状态一共设定了3个分别是
public final static int STATE_NORMAL = 0;//下拉刷新
public final static int STATE_READY = 1;//释放刷新
public final static int STATE_REFRESHING = 2;//刷新中
更新高度的方法headerView和layout都是相同的,就是原高度加上移动的距离重新赋给headerView或者layout
mHeaderView.setVisiableHeight((int) space
+ mHeaderView.getVisiableHeight());
最后就是ACTION_UP事件了就是手指离开屏幕的时候,在这里我们需要根据headerView目前状态来决定headerView的最终状态!
case MotionEvent.ACTION_UP:
//松开时
//避免点击事件触发
if (!isRefreashing)
break;
//如果headView状态处于READY状态 则说明松开时应该进入REFRESHING状态
if (mHeaderView.getStatus() == SRefreshHeader.STATE_READY) {
mHeaderView.setState(SRefreshHeader.STATE_REFRESHING);
}
//根据状态重置SrefreshLayout当前实例和headView高度
resetHeadView(mHeaderView.getStatus());
reset(mHeaderView.getStatus());
mLastY = -1;//重置坐标
break;
resetHeadView和reset分别是重置headerView高度和layout高度的方法
private void reset(int status) {
ViewGroup.LayoutParams lp = getLayoutParams();
switch (status) {
case SRefreshHeader.STATE_REFRESHING:
lp.height = mHeight + mHeaderViewHeight;
break;
case SRefreshHeader.STATE_NORMAL:
lp.height = mHeight;
break;
}
setLayoutParams(lp);
}
private void resetHeadView(int status) {
switch (status) {
case SRefreshHeader.STATE_REFRESHING:
mHeaderView.setVisiableHeight(mHeaderViewHeight);
break;
case SRefreshHeader.STATE_NORMAL:
mHeaderView.setVisiableHeight(0);
break;
}
}
实现方式也是一样的。根据状态来判断,如果是处于刷新中,那headerView应该正常显示,并且高度是初始的高度,如果处于NORMAL,也就是"下拉刷新"状态,那么说未触发刷新,重置时,headerView应该被隐藏,也就是高度重置为0
到这里下拉刷新操作也基本完成了,还需要加一个回调接口进行通知
interface OnRefreshListener {
void onRefresh();
}
case MotionEvent.ACTION_UP:
//松开时
//避免点击事件触发
if (!isRefreashing)
break;
//如果headView状态处于READY状态 则说明松开时应该进入REFRESHING状态
if (mHeaderView.getStatus() == SRefreshHeader.STATE_READY) {
mHeaderView.setState(SRefreshHeader.STATE_REFRESHING);
if (mOnRefreshListener != null)
mOnRefreshListener.onRefresh();
}
//根据状态重置SrefreshLayout当前实例和headView高度
resetHeadView(mHeaderView.getStatus());
reset(mHeaderView.getStatus());
mLastY = -1;//重置坐标
break;
好,到这里就基本完成了,试试效果吧。咦,发现一个问题,嵌套ListView的时候为什么这个Layout不能执行下拉刷新!仔细想想应该是事件分发的问题,还需要处理一下事件的拦截!
关于事件拦截的处理,阅读了鸿洋大神写的viewgroup事件分发的博客和Android-Ultra-Pull-To-Refresh的部分源码,从中找到了解决办法:
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
AbsListView absListView = null;
for (int n = 0; n < getChildCount(); n++) {
if (getChildAt(n) instanceof AbsListView) {
absListView = (ListView) getChildAt(n);
Logs.v("查找到listView");
}
}
if (absListView == null)
return super.onInterceptTouchEvent(ev);
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
mStartY = ev.getRawY();
break;
case MotionEvent.ACTION_MOVE:
float space = ev.getRawY() - mStartY;
Logs.v("space:" + space);
if (space > 0 && !absListView.canScrollVertically(-1) && absListView.getFirstVisiblePosition() == 0) {
Logs.v("拦截成功");
return true;
} else {
Logs.v("不拦截");
return false;
}
}
return super.onInterceptTouchEvent(ev);
}
其中
if (space > 0 && !absListView.canScrollVertically(-1) && absListView.getFirstVisiblePosition() == 0)
space即移动的距离 canScrollVertically()是判断ListView能否在垂直方向上滚动,参数为负数时代表向上,为正数时代码向下滚动,最后一个就是ListView第一个可见的item的postion
加上上面的事件拦截处理,一个可以满足开头提到的需求的Viewgroup也就完成了!
下面贴上Layout的源码和HeaderView(直接使用的XlistView的HeaderView)的源码
public class SRefreshLayout extends LinearLayout {
private SRefreshHeader mHeaderView;
private RelativeLayout mHeaderViewContent;
private boolean isRefreashing;
private float mLastY = -1;//按下的起始高度
private int mHeaderViewHeight;//headerView内容高度
private int mHeight;//布局高度
private float mStartY;
interface OnRefreshListener {
void onRefresh();
}
public OnRefreshListener mOnRefreshListener;
public SRefreshLayout(Context context) {
super(context);
initView(context);
}
public SRefreshLayout(Context context, AttributeSet attrs) {
super(context, attrs);
initView(context);
}
public SRefreshLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView(context);
}
private void initView(Context context) {
mHeaderView = new SRefreshHeader(context);
mHeaderViewContent = (RelativeLayout) mHeaderView.findViewById(R.id.slistview_header_content);
setOrientation(VERTICAL);
addView(mHeaderView, 0);
getHeaderViewHeight();
getViewHeight();
}
/**
* 获取headView高度
*/
private void getHeaderViewHeight() {
ViewTreeObserver vto2 = mHeaderViewContent.getViewTreeObserver();
vto2.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
mHeaderViewHeight = mHeaderViewContent.getHeight();
mHeaderViewContent.getViewTreeObserver().removeGlobalOnLayoutListener(this);
}
});
}
/**
* 获取SRefreshLayout当前实例的高度
*/
private void getViewHeight() {
ViewTreeObserver thisView = getViewTreeObserver();
thisView.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
SRefreshLayout.this.mHeight = SRefreshLayout.this.getHeight();
SRefreshLayout.this.getViewTreeObserver().removeGlobalOnLayoutListener(this);
}
});
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
AbsListView absListView = null;
for (int n = 0; n < getChildCount(); n++) {
if (getChildAt(n) instanceof AbsListView) {
absListView = (ListView) getChildAt(n);
Logs.v("查找到listView");
}
}
if (absListView == null)
return super.onInterceptTouchEvent(ev);
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
mStartY = ev.getRawY();
break;
case MotionEvent.ACTION_MOVE:
float space = ev.getRawY() - mStartY;
Logs.v("space:" + space);
if (space > 0 && !absListView.canScrollVertically(-1) && absListView.getFirstVisiblePosition() == 0) {
Logs.v("拦截成功");
return true;
} else {
Logs.v("不拦截");
return false;
}
}
return super.onInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
if (mLastY == -1)
mLastY = ev.getRawY();
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
//记录起始高度
mLastY = ev.getRawY();//记录按下时的Y坐标
break;
//手指离开屏幕时
case MotionEvent.ACTION_UP:
//松开时
//避免点击事件触发
if (!isRefreashing)
break;
//如果headView状态处于READY状态 则说明松开时应该进入REFRESHING状态
if (mHeaderView.getStatus() == SRefreshHeader.STATE_READY) {
mHeaderView.setState(SRefreshHeader.STATE_REFRESHING);
if (mOnRefreshListener != null)
mOnRefreshListener.onRefresh();
}
//根据状态重置SrefreshLayout当前实例和headView高度
resetHeadView(mHeaderView.getStatus());
reset(mHeaderView.getStatus());
mLastY = -1;//重置坐标
break;
case MotionEvent.ACTION_MOVE:
if (!isRefreashing)
isRefreashing = true;
final float deltaY = ev.getRawY() - mLastY;
mLastY = ev.getRawY();
updateHeaderViewHeight(deltaY / 1.8f);//按一定比例缩小移动距离
updateHeight();
break;
}
return super.onTouchEvent(ev);
}
private void reset(int status) {
ViewGroup.LayoutParams lp = getLayoutParams();
switch (status) {
case SRefreshHeader.STATE_REFRESHING:
lp.height = mHeight + mHeaderViewHeight;
break;
case SRefreshHeader.STATE_NORMAL:
lp.height = mHeight;
break;
}
setLayoutParams(lp);
}
private void resetHeadView(int status) {
switch (status) {
case SRefreshHeader.STATE_REFRESHING:
mHeaderView.setVisiableHeight(mHeaderViewHeight);
break;
case SRefreshHeader.STATE_NORMAL:
mHeaderView.setVisiableHeight(0);
break;
}
}
private void updateHeight() {
ViewGroup.LayoutParams lp = getLayoutParams();
//更新当前layout实例高度为headerView高度加上最初的layout高度
//如果不更新layout 会造成内容高度压缩 无法保持比例
lp.height = (mHeight + mHeaderView.getVisiableHeight());
setLayoutParams(lp);
}
private void updateHeaderViewHeight(float space) {
// if (space < 0)
// space = 0;
// int factHeight = (int) (space - mHeaderViewHeight);
if (mHeaderView.getStatus() != SRefreshHeader.STATE_REFRESHING) {
//如果不处于刷新中同时如果高度
if (mHeaderView.getVisiableHeight() < mHeaderViewHeight * 2 && mHeaderView.getStatus() != SRefreshHeader.STATE_NORMAL) {
mHeaderView.setState(SRefreshHeader.STATE_NORMAL);
}
if (mHeaderView.getVisiableHeight() > mHeaderViewHeight * 2 && mHeaderView.getStatus() != SRefreshHeader.STATE_READY) {
mHeaderView.setState(SRefreshHeader.STATE_READY);
}
}
mHeaderView.setVisiableHeight((int) space
+ mHeaderView.getVisiableHeight());
}
public void stopRefresh() {
if (mHeaderView.getStatus() == SRefreshHeader.STATE_REFRESHING) {
mHeaderView.setState(SRefreshHeader.STATE_NORMAL);
resetHeadView(SRefreshHeader.STATE_NORMAL);
reset(SRefreshHeader.STATE_NORMAL);
}
}
public void setOnRefreshListener(OnRefreshListener onRefreshListener) {
this.mOnRefreshListener = onRefreshListener;
}
}
public class SRefreshHeader extends LinearLayout {
private LinearLayout mContainer;
private int mState = STATE_NORMAL;
private Animation mRotateUpAnim;
private Animation mRotateDownAnim;
private final int ROTATE_ANIM_DURATION = 500;
public final static int STATE_NORMAL = 0;//下拉刷新
public final static int STATE_READY = 1;//释放刷新
public final static int STATE_REFRESHING = 2;//刷新中
private ImageView mHeadArrowImage;
private TextView mHeadLastRefreashTimeTxt;
private TextView mHeadHintTxt;
private TextView mHeadLastRefreashTxt;
private ProgressBar mRefreshingProgress;
public SRefreshHeader(Context context) {
super(context);
initView(context);
}
/**
* @param context
* @param attrs
*/
public SRefreshHeader(Context context, AttributeSet attrs) {
super(context, attrs);
initView(context);
}
private void initView(Context context) {
// 初始情况,设置下拉刷新view高度为0
LayoutParams lp = new LayoutParams(LayoutParams.MATCH_PARENT, 0);
mContainer = (LinearLayout) LayoutInflater.from(context).inflate(R.layout.listview_head_view_layout, null);
addView(mContainer, lp);
setGravity(Gravity.BOTTOM);
mHeadArrowImage = (ImageView) findViewById(R.id.slistview_header_arrow);
mHeadLastRefreashTimeTxt = (TextView) findViewById(R.id.slistview_header_time);
mHeadHintTxt = (TextView) findViewById(R.id.slistview_header_hint_text);
mHeadLastRefreashTxt = (TextView) findViewById(R.id.slistview_header_last_refreash_txt);
mRefreshingProgress = (ProgressBar) findViewById(R.id.slistview_header_progressbar);
mRotateUpAnim = new RotateAnimation(0.0f, -180.0f,
Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF,
0.5f);
mRotateUpAnim.setDuration(ROTATE_ANIM_DURATION);
mRotateUpAnim.setFillAfter(true);
mRotateDownAnim = new RotateAnimation(-180.0f, 0.0f,
Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF,
0.5f);
mRotateDownAnim.setDuration(ROTATE_ANIM_DURATION);
mRotateDownAnim.setFillAfter(true);
}
public void setState(int state) {
if (state == mState) return;
if (state == STATE_REFRESHING) { // 显示进度
mHeadArrowImage.clearAnimation();
mHeadArrowImage.setVisibility(View.INVISIBLE);
mRefreshingProgress.setVisibility(View.VISIBLE);
} else { // 显示箭头图片
mHeadArrowImage.setVisibility(View.VISIBLE);
mRefreshingProgress.setVisibility(View.INVISIBLE);
}
switch (state) {
case STATE_NORMAL:
if (mState == STATE_READY) {
mHeadArrowImage.startAnimation(mRotateDownAnim);
}
if (mState == STATE_REFRESHING) {
mHeadArrowImage.clearAnimation();
}
mHeadHintTxt.setText("下拉刷新");
break;
case STATE_READY:
if (mState != STATE_READY) {
mHeadArrowImage.clearAnimation();
mHeadArrowImage.startAnimation(mRotateUpAnim);
mHeadHintTxt.setText("松开刷新");
}
break;
case STATE_REFRESHING:
mHeadHintTxt.setText("正在刷新");
break;
default:
}
mState = state;
}
public void setVisiableHeight(int height) {
if (height < 0)
height = 0;
LayoutParams lp = (LayoutParams) mContainer
.getLayoutParams();
lp.height = height;
mContainer.setLayoutParams(lp);
}
public int getStatus() {
return mState;
}
public int getVisiableHeight() {
return mContainer.getHeight();
}
}
最后是布局文件
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="bottom">
<RelativeLayout
android:id="@+id/slistview_header_content"
android:layout_width="match_parent"
android:layout_height="60dp">
<LinearLayout
android:id="@+id/slistview_header_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:gravity="center"
android:orientation="vertical">
<TextView
android:id="@+id/slistview_header_hint_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="下拉刷新" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="3dp">
<TextView
android:id="@+id/slistview_header_last_refreash_txt"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="上次刷新时间"
android:textSize="12sp" />
<TextView
android:id="@+id/slistview_header_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="12sp" />
</LinearLayout>
</LinearLayout>
<ProgressBar
android:id="@+id/slistview_header_progressbar"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_centerVertical="true"
android:layout_toLeftOf="@id/slistview_header_text"
android:visibility="invisible" />
<ImageView
android:id="@+id/slistview_header_arrow"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignLeft="@id/slistview_header_progressbar"
android:layout_centerVertical="true"
android:layout_toLeftOf="@id/slistview_header_text"
android:src="@drawable/mmtlistview_arrow" />
</RelativeLayout>
</LinearLayout>
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持鸟哥教程(niaoge.com)。
(littlewhitetree)
原文链接:
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:notice#niaoge.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。