欢迎使用CSDN-markdown编辑器
2016-08-25 09:40
423 查看
分享一款通用的下拉刷新控件,PullLinearLayout
现在网上关于下拉刷新的控件有很多,大家度娘一下哗啦啦出来一大堆,或者 Git上搜一大堆,有谷歌官方的还有好多大牛写的,PullLinearLayout是公司用的控件,说它好用是因为简单,但功能完整,下面是代码时刻:
大家可以看到,它是继承于LinearLayout,这里有两个布局文件,分别是头部和底部显示的ui。
footer:
header:
至于String.xml就不贴出了,这个大家自己可以设置。
现在网上关于下拉刷新的控件有很多,大家度娘一下哗啦啦出来一大堆,或者 Git上搜一大堆,有谷歌官方的还有好多大牛写的,PullLinearLayout是公司用的控件,说它好用是因为简单,但功能完整,下面是代码时刻:
public class PullLinearLayout extends LinearLayout { // private static final String TAG = "PullLinearLayout"; //刷新状态 private static final int PULL_TO_REFRESH = 2; private static final int RELEASE_TO_REFRESH = 3; private static final int REFRESHING = 4; //滑动状态 private static final int PULL_UP_STATE = 0; private static final int PULL_DOWN_STATE = 1; private int mLastMotionY;//Y值 // private boolean mLock; private View mHeaderView; private View mFooterView; private AdapterView<?> mAdapterView;//list or grid private ScrollView mScrollView; private int mHeaderViewHeight;//头部高度 private int mFooterViewHeight;//底部高度 private ImageView mHeaderImageView; private ImageView mFooterImageView; private TextView mHeaderTextView; private TextView mFooterTextView; private TextView mHeaderUpdateTextView;//刷新时间 // private TextView mFooterUpdateTextView;//加载时间 private FrameLayout mHeaderProgressBar; private FrameLayout mFooterProgressBar; private LayoutInflater mInflater; private int mHeaderState; private int mFooterState; private int mPullState;//PULL_UP_STATE或PULL_DOWN_STATE private RotateAnimation mFlipAnimation;//变为向下的箭头,改变箭头方向 private RotateAnimation mReverseFlipAnimation;//变为逆向的箭头,旋转 private OnRefreshListener mListener; private boolean loadMoreEnabled=true; private boolean refreashEnabled=true; private float gTouchStartX, gTouchStartY; // private String mLastUpdateTime; public PullLinearLayout(Context context, AttributeSet attrs) { super(context, attrs); if (isInEditMode()) {return;} init(); } public PullLinearLayout(Context context) { super(context); if (isInEditMode()) {return;} init(); } /** * 初始化 * @param context */ private void init() { //需要设置成vertical setOrientation(LinearLayout.VERTICAL); mFlipAnimation = new RotateAnimation(0, -180, RotateAnimation.RELATIVE_TO_SELF, 0.5f, RotateAnimation.RELATIVE_TO_SELF, 0.5f); mFlipAnimation.setInterpolator(new LinearInterpolator()); mFlipAnimation.setDuration(250); mFlipAnimation.setFillAfter(true); mReverseFlipAnimation = new RotateAnimation(-180, 0, RotateAnimation.RELATIVE_TO_SELF, 0.5f, RotateAnimation.RELATIVE_TO_SELF, 0.5f); mReverseFlipAnimation.setInterpolator(new LinearInterpolator()); mReverseFlipAnimation.setDuration(250); mReverseFlipAnimation.setFillAfter(true); mInflater = LayoutInflater.from(getContext()); // header view 在此添加,保证是第一个添加到linearlayout的最上端 addHeaderView(); } private void addHeaderView() { // 头部View mHeaderView = mInflater.inflate(R.layout.zlistview_header, this, false); mHeaderImageView = (ImageView) mHeaderView .findViewById(R.id.zlistview_header_arrow); mHeaderTextView = (TextView) mHeaderView .findViewById(R.id.zlistview_header_hint_textview); mHeaderUpdateTextView = (TextView) mHeaderView .findViewById(R.id.zlistview_header_time); mHeaderProgressBar = (FrameLayout) mHeaderView .findViewById(R.id.fl_progress); measureView(mHeaderView); mHeaderViewHeight = mHeaderView.getMeasuredHeight(); LayoutParams params = new LayoutParams(LayoutParams.MATCH_PARENT, mHeaderViewHeight); // 设置topMargin的值为负的header View高度,即将其隐藏在最上方 params.topMargin = -(mHeaderViewHeight); // mHeaderView.setLayoutParams(params1); addView(mHeaderView, params); } private void addFooterView() { //底部View mFooterView = mInflater.inflate(R.layout.zlistview_footer, this, false); 4000 mFooterImageView = (ImageView) mFooterView.findViewById(R.id.pull_to_load_image); mFooterTextView = (TextView) mFooterView .findViewById(R.id.zlistview_footer_hint_textview); mFooterProgressBar = (FrameLayout) mFooterView .findViewById(R.id.fl_progress); measureView(mFooterView); mFooterViewHeight = mFooterView.getMeasuredHeight(); LayoutParams params = new LayoutParams(LayoutParams.MATCH_PARENT, mFooterViewHeight); // int top = getHeight(); // params.topMargin // =getHeight();//在这里getHeight()==0,但在onInterceptTouchEvent()方法里getHeight()已经有值了,不再是0; // getHeight()什么时候会赋值,稍候再研究一下 // 由于是线性布局可以直接添加,只要AdapterView的高度是MATCH_PARENT,那么footer view就会被添加到最后,并隐藏 addView(mFooterView, params); } public void hidFooterView(){ mFooterView.setVisibility(View.GONE); } public void showFooterView(){ mFooterView.setVisibility(View.VISIBLE); } @Override protected void onFinishInflate() { super.onFinishInflate(); // footer view 在此添加保证添加到linearlayout中的最后 addFooterView(); initContentAdapterView(); } /** * 初始化AdapterView,ListView,GridView;初始化ScrollView * */ private void initContentAdapterView() { int count = getChildCount(); if (count < 3) { throw new IllegalArgumentException( "这个View必须包含 3 子 views,并且AdapterView 或 ScrollView 必须在第二个位置!"); } View view = null; for (int i = 0; i < count - 1; ++i) { view = getChildAt(i); if (view instanceof AdapterView<?>) { mAdapterView = (AdapterView<?>) view; } if (view instanceof ScrollView) { mScrollView = (ScrollView) view; } } if (mAdapterView == null && mScrollView == null) { throw new IllegalArgumentException( "这个View必须包含 AdapterView 或 ScrollView!"); } } private void measureView(View child) { ViewGroup.LayoutParams p = child.getLayoutParams(); if (p == null) { p = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); } int childWidthSpec = ViewGroup.getChildMeasureSpec(0, 0 + 0, p.width); int lpHeight = p.height; int childHeightSpec; if (lpHeight > 0) { childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY); } else { childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); } child.measure(childWidthSpec, childHeightSpec); } @Override public boolean onInterceptTouchEvent(MotionEvent e) { int y = (int) e.getRawY(); switch (e.getAction()) { case MotionEvent.ACTION_DOWN: // 首先拦截down事件,记录y坐标 gTouchStartX = e.getX(); gTouchStartY = e.getY(); mLastMotionY = y; break; case MotionEvent.ACTION_MOVE: // deltaY > 0 是向下运动,< 0是向上运动 int deltaY = y - mLastMotionY; final float touchDistancesX = Math.abs(e.getX() - gTouchStartX); final float touchDistancesY = Math.abs(e.getY() - gTouchStartY); if (touchDistancesY * 2 >= touchDistancesX) { if (isRefreshViewScroll(deltaY)) { return true; } return false; } else { return false; } case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: break; } return false; } /* * 如果在onInterceptTouchEvent()方法中没有拦截(即onInterceptTouchEvent()方法中 return false)则 * 由PullLinearLayout 的子View来处理;否则由下面的方法来处理(即由PullLinearLayout自己来处理) */ @Override public boolean onTouchEvent(MotionEvent event) { // if (mLock) { // return true; // } int y = (int) event.getRawY(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: // onInterceptTouchEvent已经记录 // mLastMotionY = y; break; case MotionEvent.ACTION_MOVE: int deltaY = y - mLastMotionY; if (mPullState == PULL_DOWN_STATE) {//执行下拉 if(refreashEnabled){ headerPrepareToRefresh(deltaY); } // setHeaderPadding(-mHeaderViewHeight); } else if (mPullState == PULL_UP_STATE) {//执行上拉 if(loadMoreEnabled){ footerPrepareToRefresh(deltaY); } } mLastMotionY = y; break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: int topMargin = getHeaderTopMargin(); if (mPullState == PULL_DOWN_STATE) { if (topMargin >= 0) { headerRefreshing();//开始刷新 } else { setHeaderTopMargin(-mHeaderViewHeight);// 还没有执行刷新,重新隐藏 } } else if (mPullState == PULL_UP_STATE) { if (Math.abs(topMargin) >= mHeaderViewHeight + mFooterViewHeight) { footerRefreshing();// 开始执行footer 刷新 } else { setHeaderTopMargin(-mHeaderViewHeight);// 还没有执行刷新,重新隐藏 } } break; } return super.onTouchEvent(event); } /** * 是否应该到了父View,即PullLinearLayout滑动 * * @param deltaY * , deltaY > 0 是向下运动,< 0是向上运动 * @return */ private boolean isRefreshViewScroll(int deltaY) { if (mHeaderState == REFRESHING || mFooterState == REFRESHING) { return false; } //对于ListView和GridView if (mAdapterView != null) { // 子view(ListView or GridView)滑动到最顶端 if (deltaY > 0) { View child = mAdapterView.getChildAt(0); if (child == null) { // 如果mAdapterView中没有数据,不拦截 return false; } if (mAdapterView.getFirstVisiblePosition() == 0 && child.getTop() == 0) { mPullState = PULL_DOWN_STATE; return true; } int top = child.getTop(); int padding = mAdapterView.getPaddingTop(); if (mAdapterView.getFirstVisiblePosition() == 0 && Math.abs(top - padding) <= 8) {//这里之前用3可以判断,但现在不行,还没找到原因 mPullState = PULL_DOWN_STATE; return true; } } else if (deltaY < 0) { View lastChild = mAdapterView.getChildAt(mAdapterView .getChildCount() - 1); if (lastChild == null) { // 如果mAdapterView中没有数据,不拦截 return false; } // 最后一个子view的Bottom小于父View的高度说明mAdapterView的数据没有填满父view, // 等于父View的高度说明mAdapterView已经滑动到最后 if (lastChild.getBottom() <= getHeight() && mAdapterView.getLastVisiblePosition() == mAdapterView.getCount() - 1) { mPullState = PULL_UP_STATE; return true; } } } // 对于ScrollView if (mScrollView != null) { // 子scroll view滑动到最顶端 View child = mScrollView.getChildAt(0); if (deltaY > 0 && mScrollView.getScrollY() == 0) { mPullState = PULL_DOWN_STATE; return true; } else if (deltaY < 0 && child.getMeasuredHeight() <= getHeight() + mScrollView.getScrollY()) { mPullState = PULL_UP_STATE; return true; } } return false; } /** * header 准备刷新,手指移动过程,还没有释放 * * @param deltaY * ,手指滑动的距离 */ private void headerPrepareToRefresh(int deltaY) { int newTopMargin = changingHeaderViewTopMargin(deltaY); // 当header view的topMargin>=0时,说明已经完全显示出来了,修改header view 的提示状态 if (newTopMargin >= 0 && mHeaderState != RELEASE_TO_REFRESH) { mHeaderTextView.setText(R.string.zlistview_header_hint_ready); mHeaderUpdateTextView.setVisibility(View.VISIBLE); mHeaderImageView.clearAnimation(); mHeaderImageView.startAnimation(mFlipAnimation); mHeaderState = RELEASE_TO_REFRESH; } else if (newTopMargin < 0 && newTopMargin > -mHeaderViewHeight) {// 拖动时没有释放 mHeaderImageView.clearAnimation(); mHeaderImageView.startAnimation(mFlipAnimation); // mHeaderImageView. mHeaderTextView.setText(R.string.pull_to_refresh); mHeaderState = PULL_TO_REFRESH; } } /** * footer 准备刷新,手指移动过程,还没有释放 移动footer view高度同样和移动header view * 高度是一样,都是通过修改header view的topmargin的值来达到 * * @param deltaY * ,手指滑动的距离 */ private void footerPrepareToRefresh(int deltaY) { int newTopMargin = changingHeaderViewTopMargin(deltaY); // 如果header view topMargin 的绝对值大于或等于header + footer 的高度 // 说明footer view 完全显示出来了,修改footer view 的提示状态 if (Math.abs(newTopMargin) >= (mHeaderViewHeight + mFooterViewHeight) && mFooterState != RELEASE_TO_REFRESH) { mFooterTextView.setText(R.string.zlistview_footer_hint_ready); mFooterImageView.clearAnimation(); mFooterImageView.startAnimation(mFlipAnimation); mFooterState = RELEASE_TO_REFRESH; } else if (Math.abs(newTopMargin) < (mHeaderViewHeight + mFooterViewHeight)) { mFooterImageView.clearAnimation(); mFooterImageView.startAnimation(mFlipAnimation); mFooterTextView.setText(R.string.zlistview_footer_hint_normal); mFooterState = PULL_TO_REFRESH; } } /** * 修改Header view top margin的值 * * @param deltaY */ private int changingHeaderViewTopMargin(int deltaY) { LayoutParams params = (LayoutParams) mHeaderView.getLayoutParams(); float newTopMargin = params.topMargin + deltaY * 0.3f; //这里对上拉做一下限制,因为当前上拉后然后不释放手指直接下拉,会把下拉刷新给触发了,感谢网友yufengzungzhe的指出 //表示如果是在上拉后一段距离,然后直接下拉 if(deltaY>0&&mPullState == PULL_UP_STATE&&Math.abs(params.topMargin) <= mHeaderViewHeight){ return params.topMargin; } //同样地,对下拉做一下限制,避免出现跟上拉操作时一样的bug if(deltaY<0&&mPullState == PULL_DOWN_STATE&&Math.abs(params.topMargin)>=mHeaderViewHeight){ return params.topMargin; } params.topMargin = (int) newTopMargin; mHeaderView.setLayoutParams(params); invalidate(); return params.topMargin; } //头部刷新 private void headerRefreshing() { mHeaderState = REFRESHING; setHeaderTopMargin(0); mHeaderImageView.setVisibility(View.GONE); mHeaderImageView.clearAnimation(); mHeaderImageView.setImageDrawable(null); mHeaderProgressBar.setVisibility(View.VISIBLE); mHeaderTextView.setText(R.string.zlistview_header_hint_loading); if (mListener != null) { // mOnHeaderRefreshListener.onHeaderRefresh(this); mListener.onRefresh(); } } //底部更新 private void footerRefreshing() { mFooterState = REFRESHING; int top = mHeaderViewHeight + mFooterViewHeight; setHeaderTopMargin(-top); mFooterImageView.setVisibility(View.GONE); mFooterImageView.clearAnimation(); mFooterImageView.setImageDrawable(null); mFooterProgressBar.setVisibility(View.VISIBLE); mFooterTextView .setText(R.string.zlistview_header_hint_loading); if (mListener != null) { //mOnFooterRefreshListener.onFooterRefresh(this); mListener.onLoadMore(); } } /** * 设置header view 的topMargin的值 * * @param topMargin * ,为0时,说明header view 刚好完全显示出来; 为-mHeaderViewHeight时,说明完全隐藏了 */ private void setHeaderTopMargin(int topMargin) { LayoutParams params = (LayoutParams) mHeaderView.getLayoutParams(); params.topMargin = topMargin; mHeaderView.setLayoutParams(params); invalidate(); } //获取当前header view 的topMargin public void onHeaderRefreshComplete() { setHeaderTopMargin(-mHeaderViewHeight); mHeaderImageView.setVisibility(View.VISIBLE); mHeaderImageView.setImageResource(R.drawable.zlistview_arrow); mHeaderTextView.setText(R.string.pull_to_refresh); mHeaderProgressBar.setVisibility(View.GONE); // mHeaderUpdateTextView.setText(""); mHeaderState = PULL_TO_REFRESH; } //刷新后重设list到初始状态 public void onHeaderRefreshComplete(CharSequence lastUpdated) { setLastUpdated(lastUpdated); onHeaderRefreshComplete(); } //footer view 完成更新后恢复初始状态 public void onFooterRefreshComplete() { setHeaderTopMargin(-mHeaderViewHeight); mFooterImageView.setVisibility(View.VISIBLE); mFooterImageView.setImageResource(R.drawable.zlistview_arrow_up); mFooterTextView.setText(R.string.zlistview_footer_hint_normal); mFooterProgressBar.setVisibility(View.INVISIBLE); // mHeaderUpdateTextView.setText(""); mFooterState = PULL_TO_REFRESH; } //更新后设置文字提示 public void setLastUpdated(CharSequence lastUpdated) { if (lastUpdated != null) { mHeaderUpdateTextView.setVisibility(View.VISIBLE); mHeaderUpdateTextView.setText(lastUpdated); } else { mHeaderUpdateTextView.setVisibility(View.GONE); } } //获取当前header view 的topMargin private int getHeaderTopMargin() { LayoutParams params = (LayoutParams) mHeaderView.getLayoutParams(); return params.topMargin; } // private void lock() { // mLock = true; // } // private void unlock() { // mLock = false; // } //是否可以上拉加载 public void setLoadMore(boolean bool){ loadMoreEnabled=bool; } //是否可以下拉刷新 public void setRefresh(boolean bool){ refreashEnabled=bool; } public void setOnRefreshListener(OnRefreshListener listener) { mListener = listener; setLastUpdated(StringUtil.GetCurrentFormatTime()); } public interface OnRefreshListener { void onRefresh();//刷新操作 void onLoadMore();//加载操作 } }
大家可以看到,它是继承于LinearLayout,这里有两个布局文件,分别是头部和底部显示的ui。
footer:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="wrap_content" > <RelativeLayout android:id="@+id/zlistview_footer_content" android:layout_width="fill_parent" android:layout_height="wrap_content" android:padding="10dp" > <FrameLayout android:id="@+id/fl_progress" android:layout_width="30dp" android:layout_height="30dp" android:layout_centerVertical="true" android:layout_gravity="center" android:layout_marginRight="20dp" android:layout_toLeftOf="@+id/zlistview_footer_hint_textview" android:visibility="invisible" > <ProgressBar android:id="@+id/zlistview_footer_progressbar" android:layout_width="30dp" android:layout_height="30dp" android:layout_gravity="center" /> <ImageView android:layout_width="30dp" android:layout_height="30dp" android:layout_gravity="center" android:padding="7dp" android:src="@drawable/none" /> </FrameLayout> <ImageView android:id="@+id/zlistview_footer_nomore" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:layout_toLeftOf="@+id/zlistview_footer_hint_textview" android:paddingRight="10dp" android:src="@drawable/nomore" android:visibility="invisible" /> <ImageView android:id="@+id/pull_to_load_image" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:layout_marginLeft="30dp" android:layout_marginRight="20dp" android:layout_toLeftOf="@+id/zlistview_footer_hint_textview" android:gravity="center" android:src="@drawable/zlistview_arrow_up" android:visibility="invisible" /> <TextView android:id="@+id/zlistview_footer_hint_textview" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:text="@string/zlistview_footer_hint_normal" android:textColor="@color/black_666" android:textSize="@dimen/font_size_14dp" /> </RelativeLayout> </LinearLayout>
header:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="wrap_content" android:gravity="bottom" > <RelativeLayout android:id="@+id/zlistview_header_content" android:layout_width="fill_parent" android:layout_height="60dp" > <LinearLayout android:id="@+id/zlistview_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/zlistview_header_hint_textview" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/pull_to_refresh" android:textColor="@color/black_666" android:textSize="@dimen/font_size_14dp" /> <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="3dp" android:textColor="@color/black_666" android:textSize="@dimen/font_size_12dp" > <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/last_update_time" android:textColor="@color/black_666" android:textSize="@dimen/font_size_12dp" /> <TextView android:id="@+id/zlistview_header_time" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textColor="@color/black_666" android:textSize="@dimen/font_size_12dp" /> </LinearLayout> </LinearLayout> <ImageView android:id="@+id/zlistview_header_arrow" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignLeft="@id/zlistview_header_text" android:layout_centerVertical="true" android:layout_marginLeft="-35dp" android:src="@drawable/zlistview_arrow" /> <FrameLayout android:id="@+id/fl_progress" android:layout_width="30dp" android:layout_height="30dp" android:layout_alignLeft="@id/zlistview_header_text" android:layout_centerVertical="true" android:layout_gravity="center" android:layout_marginLeft="-40dp" android:visibility="invisible" > <ProgressBar android:id="@+id/zlistview_header_progressbar" android:layout_width="30dp" android:layout_height="30dp" android:layout_gravity="center" /> <ImageView android:layout_width="30dp" android:layout_height="30dp" android:layout_gravity="center" android:padding="7dp" android:src="@drawable/none" /> </FrameLayout> </RelativeLayout> </LinearLayout>
至于String.xml就不贴出了,这个大家自己可以设置。
相关文章推荐
- 欢迎使用CSDN-markdown编辑器
- 欢迎使用CSDN-markdown编辑器
- 欢迎使用CSDN-markdown编辑器
- 欢迎使用CSDN-markdown编辑器
- 欢迎使用CSDN-markdown编辑器
- 欢迎使用CSDN-markdown编辑器
- 欢迎使用CSDN-markdown编辑器
- 欢迎使用CSDN-markdown编辑器
- 欢迎使用CSDN-markdown编辑器
- 欢迎使用CSDN-markdown编辑器
- 欢迎使用CSDN-markdown编辑器
- 欢迎使用CSDN-markdown编辑器
- 欢迎使用CSDN-markdown编辑器
- 欢迎使用CSDN-markdown编辑器ss
- 欢迎使用CSDN-markdown编辑器
- 欢迎使用CSDN-markdown编辑器
- 欢迎使用CSDN-markdown编辑器
- 欢迎使用CSDN-markdown编辑器
- 欢迎使用CSDN-markdown编辑器
- 欢迎使用CSDN-markdown编辑器