安卓listView实现下拉刷新上拉加载滑动仿QQ的删除功能
大家对这些功能都是看的多了,然后对上拉刷新和下拉加载的原理都是非常清楚的,所以实现这功能其实也就是为了让大家能够从众多的同行们来进行比较学习而已,虽然即使是这样,但是面试的时候面试官还是会问你上拉和下拉是怎么实现的,滑动删除功能是怎么实现,其实要实现这些功能又不是唯一的方法,但是基本上思想都是一致的。然后githup上的这些例子是非常的多,然后实现的也是大同小异但是也不能不让我们去球童存异。作为天朝的程序员即使是一个伸手党也不必太觉得羞耻,能把别人的东西来改一改或者沿用别人的思想来模仿也是不错的。然后公司的项目也可能不建议直接去导入XlistView来实现下拉的功能,所以我们可能就需要自己去实现一些这样的功能了,废话不多说,稍微就介绍下原理,主要还是代码为主,即使看到代码和别人大同小异也不要计较,因为这些功能纯粹就是模仿好了,当然参考别人的代码也不是什么丢脸的事,关键的是对自己有帮助就好了啊。
我们要写这样一个功能当然的先从自定义的listView开始了,说起来其实就是一个组合控件,它就由3部分组成:head 、content、footer,所以我们的代码也就可以分开来实现了额。先我们写头部的布局和功能。
xlistview_header.xml
xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="bottom" > <RelativeLayout android:id="@+id/xlistview_header_content" android:layout_width="match_parent" android:layout_height="60dp" android:layout_weight="1" tools:ignore="UselessParent" > <LinearLayout android:id="@+id/xlistview_header" android:layout_width="wrap_content" android:layout_height="fill_parent" android:layout_centerInParent="true" android:layout_marginLeft="10dp" android:layout_marginTop="15dp" android:orientation="vertical" > <TextView android:id="@+id/xlistview_header_hint_textview" android:layout_width="180dp" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:layout_marginLeft="10dp" android:text="正在加载" android:textColor="@android:color/black" android:textSize="18sp" /> <TextView android:id="@+id/head_lastUpdatedTextView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="5dp" android:text="上次更新:" android:textSize="12dp" /> LinearLayout> <ImageView android:id="@+id/xlistview_header_arrow" android:layout_width="30dp" android:layout_height="wrap_content" android:layout_centerVertical="true" android:layout_toLeftOf="@id/xlistview_header" android:src="@drawable/zlistview_arrow" /> <ProgressBar android:id="@+id/xlistview_header_progressbar" android:layout_width="30dp" android:layout_height="30dp" android:layout_centerVertical="true" android:layout_toLeftOf="@id/xlistview_header" android:visibility="invisible" /> RelativeLayout> LinearLayout>
很容易发现头部就是由刷新加载数据时显示图片,头部中间显示加载的状态文字,如果是第一次不会提醒上次更新时间,如果不是第一次就会显示上次的刷新时间,然后手指下拉时箭头向下松开时箭头向上,移动时显示圆形加载进度。好了实现的思路也了解了吧。
ZListViewHeader.java(头部布局)
package com.zy.zlistview.widget; import android.content.Context; import android.content.SharedPreferences; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.View; import android.view.animation.Animation; import android.view.animation.RotateAnimation; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.ProgressBar; import android.widget.TextView; import com.socks.zlistview.R; public class ZListViewHeader extends LinearLayout {//手势动作变化,状态的三种描述 private static final String HINT_NORMAL = "下拉刷新"; private static final String HINT_READY = "松开刷新数据"; private static final String HINT_LOADING = "正在加载..."; // 正常状态 public final static int STATE_NORMAL = 0; // 准备刷新状态,也就是箭头方向发生改变之后的状态 public final static int STATE_READY = 1; // 刷新状态,箭头变成了progressBar public final static int STATE_REFRESHING = 2; // 布局容器,也就是根布局 private LinearLayout container; // 箭头图片 private ImageView mArrowImageView; // 刷新状态显示 private ProgressBar mProgressBar; // 说明文本 private TextView mHintTextView; // 记录当前的状态 private int mState; // 用于改变箭头的方向的动画 private Animation mRotateUpAnim; private Animation mRotateDownAnim; // 动画持续时间 private final int ROTATE_ANIM_DURATION = 180; //x显示上次刷新的时间 private TextView head_lastUpdatedTextView; protected TextView getHead_lastUpdatedTextView() {return head_lastUpdatedTextView; }public void setHead_lastUpdatedTextView(TextView head_lastUpdatedTextView) {this.head_lastUpdatedTextView = head_lastUpdatedTextView; }public ZListViewHeader(Context context) {super(context); initView(context); }public ZListViewHeader(Context context, AttributeSet attrs) {super(context, attrs); initView(context); }private void initView(Context context) {mState = STATE_NORMAL; // 初始情况下,设置下拉刷新view高度为0 LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, 0); container = (LinearLayout) LayoutInflater.from(context).inflate(R.layout.xlistview_header, null); addView(container, lp); // 初始化控件 mArrowImageView = (ImageView) findViewById(R.id.xlistview_header_arrow); head_lastUpdatedTextView = (TextView) findViewById(R.id.head_lastUpdatedTextView); head_lastUpdatedTextView.setVisibility(View.INVISIBLE); mHintTextView = (TextView) findViewById(R.id.xlistview_header_hint_textview); mProgressBar = (ProgressBar) findViewById(R.id.xlistview_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); }// 设置header的状态 public void setState(int state) {if (state == mState)return; // 显示进度 if (state == STATE_REFRESHING) {//刷新时 mArrowImageView.clearAnimation();//箭头的动画消失,并且替换为显示精度条 mArrowImageView.setVisibility(View.INVISIBLE); mProgressBar.setVisibility(View.VISIBLE); } else { // 显示箭头 mArrowImageView.setVisibility(View.VISIBLE); mProgressBar.setVisibility(View.INVISIBLE); }switch (state) {case STATE_NORMAL: if (mState == STATE_READY) {mArrowImageView.startAnimation(mRotateDownAnim); }if (mState == STATE_REFRESHING) {mArrowImageView.clearAnimation(); SharedPreferences sp = getContext().getSharedPreferences("isResh", 0); if (sp.getBoolean("isresh", true) == true) {head_lastUpdatedTextView.setVisibility(View.VISIBLE); head_lastUpdatedTextView.setText("上次更新:" + sp.getString("date", "")); }}mHintTextView.setText(HINT_NORMAL); break; case STATE_READY: if (mState != STATE_READY) {mArrowImageView.clearAnimation(); mArrowImageView.startAnimation(mRotateUpAnim); mHintTextView.setText(HINT_READY);//显示提示文字 }break; case STATE_REFRESHING: mHintTextView.setText(HINT_LOADING); break; }mState = state; }public void setVisiableHeight(int height) {//设置头部可视高度 if (height < 0)height = 0; LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) container.getLayoutParams(); lp.height = height; container.setLayoutParams(lp); }public int getVisiableHeight() {return container.getHeight(); }public void show() {container.setVisibility(View.VISIBLE); }public void hide() {container.setVisibility(View.INVISIBLE); } }
接下来看底部:xlistview_footer.xml
xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="fill_parent" android:layout_height="wrap_content" > <RelativeLayout android:id="@+id/xlistview_footer_content" android:layout_width="fill_parent" android:layout_height="wrap_content" android:padding="5dp" tools:ignore="UselessParent" > <LinearLayout android:id="@+id/footer_load" android:layout_width="fill_parent" android:layout_height="wrap_content" android:gravity="center_horizontal" android:visibility="invisible" > <ProgressBar android:id="@+id/xlistview_footer_progressbar" android:layout_width="30dp" android:layout_height="30dp" android:layout_gravity="center" android:visibility="invisible" /> <TextView android:id="@+id/xlistview_footer_loding_textview" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:paddingLeft="8dp" android:textColor="@android:color/black" android:textSize="14sp" android:visibility="invisible" /> LinearLayout> <TextView android:id="@+id/xlistview_footer_hint_textview" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:textColor="@android:color/black" android:textSize="14sp" /> RelativeLayout> LinearLayout>
底部的布局就更加的简单了,正常情况下就一个文字提示你加载更多,当你上拉时就变为显示进度条和加载状态。功能实现也不是很复杂。直接代码:
ZListViewFooter.java
package com.zy.zlistview.view; import android.content.Context; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.View; import android.widget.LinearLayout; import android.widget.TextView; import com.socks.zlistview.R; public class ZListViewFooter extends LinearLayout {public final static String HINT_READ = "松开载入更多"; public final static String HINT_NORMAL = "查看更多"; // 正常状态 public final static int STATE_NORMAL = 0; // 准备状态 public final static int STATE_READY = 1; // 加载状态 public final static int STATE_LOADING = 2; private View mContentView; private View mProgressBar; private TextView mHintView; private LinearLayout footer_load; private TextView loding_textview; public ZListViewFooter(Context context) {super(context); initView(context); }public ZListViewFooter(Context context, AttributeSet attrs) {super(context, attrs); initView(context); }private void initView(Context context) {LinearLayout moreView = (LinearLayout) LayoutInflater.from(context).inflate(R.layout.xlistview_footer, null); addView(moreView); moreView.setLayoutParams(new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)); mContentView = moreView.findViewById(R.id.xlistview_footer_content); footer_load= (LinearLayout) moreView.findViewById(R.id.footer_load); loding_textview= (TextView) moreView.findViewById(R.id.xlistview_footer_loding_textview); mProgressBar = moreView.findViewById(R.id.xlistview_footer_progressbar); mHintView = (TextView) moreView .findViewById(R.id.xlistview_footer_hint_textview); }/** * 设置当前的状态 * * @param state */ public void setState(int state) {//mProgressBar.setVisibility(View.INVISIBLE); footer_load.setVisibility(View.INVISIBLE); mHintView.setVisibility(View.INVISIBLE); switch (state) {case STATE_READY://准备状态 mHintView.setVisibility(View.VISIBLE); mHintView.setText(HINT_READ); break; case STATE_NORMAL://正常状态 mHintView.setVisibility(View.VISIBLE); mHintView.setText(HINT_NORMAL); break; case STATE_LOADING: footer_load.setVisibility(View.VISIBLE); mProgressBar.setVisibility(View.VISIBLE); loding_textview.setVisibility(View.VISIBLE); loding_textview.setText("正在加载中...."); break; }}public void setBottomMargin(int height) {if (height > 0) {LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) mContentView .getLayoutParams(); lp.bottomMargin = height; mContentView.setLayoutParams(lp); }}public int getBottomMargin() {LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) mContentView .getLayoutParams(); return lp.bottomMargin; }public void hide() {LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) mContentView .getLayoutParams(); lp.height = 0; mContentView.setLayoutParams(lp); }public void show() {LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) mContentView .getLayoutParams(); lp.height = LayoutParams.WRAP_CONTENT; mContentView.setLayoutParams(lp); } }
跟实现头部的代码差不多,主要就是一些状态改变然后布局做相应的改变。
接下来是中间item的布局,然后是模仿qq的滑动删除的功能,所以我们还是参考了gitup上的开源项目SwipeXXX,你可以自己去看下,然后参考了里面的很多东西,不多说了直接代码
SwipeListener.java
package com.zy.zlistview.listener; import com.zy.zlistview.view.ZSwipeItem; public interface SwipeListener {public void onStartOpen(ZSwipeItem layout); public void onOpen(ZSwipeItem layout); public void onStartClose(ZSwipeItem layout); public void onClose(ZSwipeItem layout); public void onUpdate(ZSwipeItem layout, int leftOffset, int topOffset); public void onHandRelease(ZSwipeItem layout, float xvel, float yvel); }
然后我们设了几个枚举类方便我们对item的控制
public enum DragEdge {Left, Right, Top, Bottom; }; public enum Mode {Single, Multiple; }; public enum ShowMode {LayDown, PullOut; }
我们先来参考QQ条目的布局实现:
package com.zy.zlistview.view; import java.util.ArrayList; import java.util.List; import android.annotation.SuppressLint; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Rect; import android.support.v4.view.ViewCompat; import android.support.v4.widget.ViewDragHelper; import android.util.AttributeSet; import android.util.Log; import android.view.GestureDetector; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.ViewParent; import android.widget.Adapter; import android.widget.AdapterView; import android.widget.BaseAdapter; import android.widget.FrameLayout; import android.widget.ListAdapter; import com.socks.zlistview.R; import com.zy.zlistview.adapter.BaseSwipeAdapter.OnSwipeLayoutListener; import com.zy.zlistview.enums.DragEdge; import com.zy.zlistview.enums.ShowMode; import com.zy.zlistview.listener.SwipeListener; @SuppressLint("ClickableViewAccessibility") public class ZSwipeItem extends FrameLayout {protected static final String TAG = "ZSwipeItem"; private ViewDragHelper mDragHelper; private int mDragDistance = 0; private DragEdge mDragEdge; private ShowMode mShowMode; private float mHorizontalSwipeOffset; private float mVerticalSwipeOffset; private boolean mSwipeEnabled = true; private List<OnSwipeLayoutListener> mOnLayoutListeners; private List<SwipeListener> swipeListeners = new ArrayList<SwipeListener>(); public ZSwipeItem(Context context) {this(context, null); }public ZSwipeItem(Context context, AttributeSet attrs) {this(context, attrs, 0); }public ZSwipeItem(Context context, AttributeSet attrs, int defStyle) {super(context, attrs, defStyle); mDragHelper = ViewDragHelper.create(this, mDragHelperCallback); TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ZSwipeItem); // 默认是右边缘检测 int ordinal = a.getInt(R.styleable.ZSwipeItem_drag_edge, DragEdge.Right.ordinal()); mDragEdge = DragEdge.values()[ordinal]; // 默认模式是拉出 ordinal = a.getInt(R.styleable.ZSwipeItem_show_mode, ShowMode.PullOut.ordinal()); mShowMode = ShowMode.values()[ordinal]; mHorizontalSwipeOffset = a.getDimension(R.styleable.ZSwipeItem_horizontalSwipeOffset, 0); mVerticalSwipeOffset = a.getDimension(R.styleable.ZSwipeItem_verticalSwipeOffset, 0); a.recycle(); }/** * 进行拖拽的主要类 */ private ViewDragHelper.Callback mDragHelperCallback = new ViewDragHelper.Callback() {/** * 计算被横向拖动view的left */ @Override public int clampViewPositionHorizontal(View child, int left, int dx) {if (child == getSurfaceView()) {switch (mDragEdge) {case Top: case Bottom: return getPaddingLeft(); case Left: if (left < getPaddingLeft())return getPaddingLeft(); if (left > getPaddingLeft() + mDragDistance)return getPaddingLeft() + mDragDistance; break; case Right: if (left > getPaddingLeft())return getPaddingLeft(); if (left < getPaddingLeft() - mDragDistance)return getPaddingLeft() - mDragDistance; break; }} else if (child == getBottomView()) {switch (mDragEdge) {case Top: case Bottom: return getPaddingLeft(); case Left: if (mShowMode == ShowMode.PullOut) {if (left > getPaddingLeft())return getPaddingLeft(); }break; case Right: if (mShowMode == ShowMode.PullOut) {if (left < getMeasuredWidth() - mDragDistance) {return getMeasuredWidth() - mDragDistance; }}break; }}return left; }/** * 计算被纵向拖动的view的top */ @Override public int clampViewPositionVertical(View child, int top, int dy) {if (child == getSurfaceView()) {switch (mDragEdge) {case Left: case Right: return getPaddingTop(); case Top: if (top < getPaddingTop())return getPaddingTop(); if (top > getPaddingTop() + mDragDistance)return getPaddingTop() + mDragDistance; break; case Bottom: if (top < getPaddingTop() - mDragDistance) {return getPaddingTop() - mDragDistance; }if (top > getPaddingTop()) {return getPaddingTop(); }}} else {switch (mDragEdge) {case Left: case Right: return getPaddingTop(); case Top: if (mShowMode == ShowMode.PullOut) {if (top > getPaddingTop())return getPaddingTop(); } else {if (getSurfaceView().getTop() + dy < getPaddingTop())return getPaddingTop(); if (getSurfaceView().getTop() + dy > getPaddingTop()+ mDragDistance)return getPaddingTop() + mDragDistance; }break; case Bottom: if (mShowMode == ShowMode.PullOut) {if (top < getMeasuredHeight() - mDragDistance)return getMeasuredHeight() - mDragDistance; } else {if (getSurfaceView().getTop() + dy >= getPaddingTop())return getPaddingTop(); if (getSurfaceView().getTop() + dy <= getPaddingTop()- mDragDistance)return getPaddingTop() - mDragDistance; }}}return top; }/** * 确定要进行拖动的view */ @Override public boolean tryCaptureView(View child, int pointerId) {return child == getSurfaceView() || child == getBottomView(); }/** * 确定横向拖动边界 */ @Override public int getViewHorizontalDragRange(View child) {return mDragDistance; }/** * 确定纵向拖动边界 */ @Override public int getViewVerticalDragRange(View child) {return mDragDistance; }/** * 当子控件被释放的时候调用,可以获取加速度的数据,来判断用户意图 */ @Override public void onViewReleased(View releasedChild, float xvel, float yvel) {super.onViewReleased(releasedChild, xvel, yvel); for (SwipeListener l : swipeListeners) {l.onHandRelease(ZSwipeItem.this, xvel, yvel); }if (releasedChild == getSurfaceView()) {processSurfaceRelease(xvel, yvel); } else if (releasedChild == getBottomView()) {if (getShowMode() == ShowMode.PullOut) {processBottomPullOutRelease(xvel, yvel); } else if (getShowMode() == ShowMode.LayDown) {processBottomLayDownMode(xvel, yvel); }}invalidate(); }/** * 当view的位置发生变化的时候调用,可以设置view的位置跟随手指移动 */ @Override public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {int evLeft = getSurfaceView().getLeft(); int evTop = getSurfaceView().getTop(); if (changedView == getSurfaceView()) {if (mShowMode == ShowMode.PullOut) {if (mDragEdge == DragEdge.Left|| mDragEdge == DragEdge.Right) {getBottomView().offsetLeftAndRight(dx); } else {getBottomView().offsetTopAndBottom(dy); }}} else if (changedView == getBottomView()) {if (mShowMode == ShowMode.PullOut) {getSurfaceView().offsetLeftAndRight(dx); getSurfaceView().offsetTopAndBottom(dy); } else {Rect rect = computeBottomLayDown(mDragEdge); getBottomView().layout(rect.left, rect.top, rect.right, rect.bottom); int newLeft = getSurfaceView().getLeft() + dx; int newTop = getSurfaceView().getTop() + dy; if (mDragEdge == DragEdge.Left&& newLeft < getPaddingLeft())newLeft = getPaddingLeft(); else if (mDragEdge == DragEdge.Right&& newLeft > getPaddingLeft())newLeft = getPaddingLeft(); else if (mDragEdge == DragEdge.Top&& newTop < getPaddingTop())newTop = getPaddingTop(); else if (mDragEdge == DragEdge.Bottom&& newTop > getPaddingTop())newTop = getPaddingTop(); getSurfaceView().layout(newLeft, newTop, newLeft + getMeasuredWidth(), newTop + getMeasuredHeight()); }}// 及时派发滑动事件 dispatchSwipeEvent(evLeft, evTop, dx, dy); invalidate(); }}; /** * swipe事件分发器 * * @param surfaceLeft * @param surfaceTop * @param dx * @param dy */ protected void dispatchSwipeEvent(int surfaceLeft, int surfaceTop, int dx, int dy) {DragEdge edge = getDragEdge(); boolean open = true; if (edge == DragEdge.Left) {if (dx < 0)open = false; } else if (edge == DragEdge.Right) {if (dx > 0)open = false; } else if (edge == DragEdge.Top) {if (dy < 0)open = false; } else if (edge == DragEdge.Bottom) {if (dy > 0)open = false; }dispatchSwipeEvent(surfaceLeft, surfaceTop, open); }private int mEventCounter = 0; /** * swipe事件分发器 * * @param surfaceLeft * @param surfaceTop * @param open */ protected void dispatchSwipeEvent(int surfaceLeft, int surfaceTop, boolean open) {safeBottomView(); Status status = getOpenStatus(); if (!swipeListeners.isEmpty()) {Log.d(TAG, "swipeListeners=" + swipeListeners.size()); mEventCounter++; if (mEventCounter == 1) {if (open) {swipeListeners.get(0).onStartOpen(ZSwipeItem.this); swipeListeners.get(swipeListeners.size() - 1).onStartOpen(ZSwipeItem.this); } else {swipeListeners.get(0).onStartClose(ZSwipeItem.this); swipeListeners.get(swipeListeners.size() - 1).onStartClose(ZSwipeItem.this); }}for (SwipeListener l : swipeListeners) {l.onUpdate(ZSwipeItem.this, surfaceLeft - getPaddingLeft(), surfaceTop - getPaddingTop()); }if (status == Status.Close) {swipeListeners.get(0).onClose(ZSwipeItem.this); swipeListeners.get(swipeListeners.size() - 1).onClose(ZSwipeItem.this); mEventCounter = 0; } else if (status == Status.Open) {getBottomView().setEnabled(true); swipeListeners.get(0).onOpen(ZSwipeItem.this); swipeListeners.get(swipeListeners.size() - 1).onOpen(ZSwipeItem.this); mEventCounter = 0; }}}/** * 防止底布局获取到任何的触摸事件,特别是在LayDown模式 */ private void safeBottomView() {Status status = getOpenStatus(); ViewGroup bottom = getBottomView(); if (status == Status.Close) {if (bottom.getVisibility() != INVISIBLE)bottom.setVisibility(INVISIBLE); } else {if (bottom.getVisibility() != VISIBLE)bottom.setVisibility(VISIBLE); }}@Override public void computeScroll() {super.computeScroll(); // 让滚动一直进行下去 if (mDragHelper.continueSettling(true)) {ViewCompat.postInvalidateOnAnimation(this); }}/** * 强制布局中必须嵌套两个ViewGroup布局,在新item出现的时候就会调用 */ @SuppressLint("WrongCall")@Override protected void onLayout(boolean changed, int l, int t, int r, int b) {int childCount = getChildCount(); if (childCount != 2) {throw new IllegalStateException("You need 2 views in SwipeLayout"); }if (!(getChildAt(0) instanceof ViewGroup)|| !(getChildAt(1) instanceof ViewGroup)) {throw new IllegalArgumentException("The 2 children in SwipeLayout must be an instance of ViewGroup"); }if (mShowMode == ShowMode.PullOut) {layoutPullOut(); } else if (mShowMode == ShowMode.LayDown) {layoutLayDown(); }safeBottomView(); if (mOnLayoutListeners != null)for (int i = 0; i < mOnLayoutListeners.size(); i++) {mOnLayoutListeners.get(i).onLayout(this); }}private void layoutPullOut() {Rect rect = computeSurfaceLayoutArea(false); getSurfaceView().layout(rect.left, rect.top, rect.right, rect.bottom); rect = computeBottomLayoutAreaViaSurface(ShowMode.PullOut, rect); getBottomView().layout(rect.left, rect.top, rect.right, rect.bottom); bringChildToFront(getSurfaceView()); }private void layoutLayDown() {Rect rect = computeSurfaceLayoutArea(false); getSurfaceView().layout(rect.left, rect.top, rect.right, rect.bottom); rect = computeBottomLayoutAreaViaSurface(ShowMode.LayDown, rect); getBottomView().layout(rect.left, rect.top, rect.right, rect.bottom); bringChildToFront(getSurfaceView()); }@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {super.onMeasure(widthMeasureSpec, heightMeasureSpec); // 初始化移动距离 if (mDragEdge == DragEdge.Left || mDragEdge == DragEdge.Right)mDragDistance = getBottomView().getMeasuredWidth()- dp2px(mHorizontalSwipeOffset); else {mDragDistance = getBottomView().getMeasuredHeight()- dp2px(mVerticalSwipeOffset); }}private boolean mTouchConsumedByChild = false; @Override public boolean onInterceptTouchEvent(MotionEvent ev) {if (!isEnabled() || !isEnabledInAdapterView()) {return true; }if (!isSwipeEnabled()) {return false; }int action = ev.getActionMasked(); switch (action) {case MotionEvent.ACTION_DOWN: Status status = getOpenStatus(); if (status == Status.Close) {mTouchConsumedByChild = childNeedHandleTouchEvent(getSurfaceView(), ev) != null; } else if (status == Status.Open) {mTouchConsumedByChild = childNeedHandleTouchEvent(getBottomView(), ev) != null; }break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: mTouchConsumedByChild = false; }if (mTouchConsumedByChild)return false; return mDragHelper.shouldInterceptTouchEvent(ev); }/** * ViewGroup的子View是否需要处理这个事件 * * @param v * @param event * @return */ private View childNeedHandleTouchEvent(ViewGroup v, MotionEvent event) {if (v == null)return null; if (v.onTouchEvent(event))return v; int childCount = v.getChildCount(); for (int i = childCount - 1; i >= 0; i--) {View child = v.getChildAt(i); if (child instanceof ViewGroup) {View grandChild = childNeedHandleTouchEvent((ViewGroup) child, event); if (grandChild != null)return grandChild; } else {if (childNeedHandleTouchEvent(v.getChildAt(i), event))return v.getChildAt(i); }}return null; }/** * 判断View是否要去处理触摸事件 * * @param v * @param event * @return */ private boolean childNeedHandleTouchEvent(View v, MotionEvent event) {if (v == null)return false; int[] loc = new int[2]; v.getLocationOnScreen(loc); int left = loc[0]; int top = loc[1]; if (event.getRawX() > left && event.getRawX() < left + v.getWidth()&& event.getRawY() > top && event.getRawY() < top + v.getHeight()) {return v.onTouchEvent(event); }return false; }private float sX = -1, sY = -1; @Override public boolean onTouchEvent(MotionEvent event) {if (!isEnabledInAdapterView() || !isEnabled())return true; if (!isSwipeEnabled())return super.onTouchEvent(event); int action = event.getActionMasked(); ViewParent parent = getParent(); gestureDetector.onTouchEvent(event); Status status = getOpenStatus(); ViewGroup touching = null; if (status == Status.Close) {touching = getSurfaceView(); } else if (status == Status.Open) {touching = getBottomView(); }switch (action) {case MotionEvent.ACTION_DOWN: mDragHelper.processTouchEvent(event); parent.requestDisallowInterceptTouchEvent(true); sX = event.getRawX(); sY = event.getRawY(); if (touching != null)touching.setPressed(true); return true; case MotionEvent.ACTION_MOVE: {if (sX == -1 || sY == -1) { // Trick: // When in nested mode, we need to send a constructed // ACTION_DOWN MotionEvent to mDragHelper, to help // it initialize itself. event.setAction(MotionEvent.ACTION_DOWN); mDragHelper.processTouchEvent(event); parent.requestDisallowInterceptTouchEvent(true); sX = event.getRawX(); sY = event.getRawY(); return true; }float distanceX = event.getRawX() - sX; float distanceY = event.getRawY() - sY; float angle = Math.abs(distanceY / distanceX); angle = (float) Math.toDegrees(Math.atan(angle)); boolean doNothing = false; // 根据触摸角度,判断是否执行用户操作 if (mDragEdge == DragEdge.Right) {boolean suitable = (status == Status.Open && distanceX > 0)|| (status == Status.Close && distanceX < 0); suitable = suitable || (status == Status.Middle); if (angle > 30 || !suitable) {doNothing = true; }}if (mDragEdge == DragEdge.Left) {boolean suitable = (status == Status.Open && distanceX < 0)|| (status == Status.Close && distanceX > 0); suitable = suitable || status == Status.Middle; if (angle > 30 || !suitable) {doNothing = true; }}if (mDragEdge == DragEdge.Top) {boolean suitable = (status == Status.Open && distanceY < 0)|| (status == Status.Close && distanceY > 0); suitable = suitable || status == Status.Middle; if (angle < 60 || !suitable) {doNothing = true; }}if (mDragEdge == DragEdge.Bottom) {boolean suitable = (status == Status.Open && distanceY > 0)|| (status == Status.Close && distanceY < 0); suitable = suitable || status == Status.Middle; if (angle < 60 || !suitable) {doNothing = true; }}if (doNothing) { // 拦截触摸事件 parent.requestDisallowInterceptTouchEvent(false); return false; } else {if (touching != null) {touching.setPressed(false); }parent.requestDisallowInterceptTouchEvent(true); mDragHelper.processTouchEvent(event); }break; }case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: {sX = -1; sY = -1; if (touching != null) {touching.setPressed(false); }}default: parent.requestDisallowInterceptTouchEvent(true); mDragHelper.processTouchEvent(event); }return true; }/** * if working in {@link android.widget.AdapterView}, we should response * {@link android.widget.Adapter} isEnable(int position). * * @return true when item is enabled, else disabled. */ private boolean isEnabledInAdapterView() {@SuppressWarnings("rawtypes")AdapterView adapterView = getAdapterView(); boolean enable = true; if (adapterView != null) {Adapter adapter = adapterView.getAdapter(); if (adapter != null) {int p = adapterView.getPositionForView(ZSwipeItem.this); if (adapter instanceof BaseAdapter) {enable = ((BaseAdapter) adapter).isEnabled(p); } else if (adapter instanceof ListAdapter) {enable = ((ListAdapter) adapter).isEnabled(p); }}}return enable; }public void setSwipeEnabled(boolean enabled) {mSwipeEnabled = enabled; }public boolean isSwipeEnabled() {return mSwipeEnabled; }@SuppressWarnings("rawtypes")private AdapterView getAdapterView() {ViewParent t = getParent(); while (t != null) {if (t instanceof AdapterView) {return (AdapterView) t; }t = t.getParent(); }return null; }private void performAdapterViewItemClick(MotionEvent e) {ViewParent t = getParent(); Log.d(TAG, "performAdapterViewItemClick()"); while (t != null) {if (t instanceof AdapterView) {@SuppressWarnings("rawtypes")AdapterView view = (AdapterView) t; int p = view.getPositionForView(ZSwipeItem.this); if (p != AdapterView.INVALID_POSITION && view.performItemClick(view.getChildAt(p - view.getFirstVisiblePosition()), p, view.getAdapter().getItemId(p)))return; } else {if (t instanceof View && ((View) t).performClick())return; }t = t.getParent(); }}private GestureDetector gestureDetector = new GestureDetector(getContext(), new SwipeDetector()); /** * 手势监听器,通过调用performItemClick、performItemLongClick,来解决item的点击问题, * * */ private class SwipeDetector extends GestureDetector.SimpleOnGestureListener {@Override public boolean onDown(MotionEvent e) {return true; }@Override public boolean onSingleTapUp(MotionEvent e) { // 当用户单击之后,手指抬起的时候调用,如果没有双击监听器,就直接调用 performAdapterViewItemClick(e); return true; }@Override public boolean onSingleTapConfirmed(MotionEvent e) { // 这个方法只有在确认用户不会发生双击事件的时候调用 return false; }@Override public void onLongPress(MotionEvent e) { // 长按事件 performLongClick(); }@Override public boolean onDoubleTap(MotionEvent e) {return false; }}public void setDragEdge(DragEdge dragEdge) {mDragEdge = dragEdge; requestLayout(); }public void setShowMode(ShowMode mode) {mShowMode = mode; requestLayout(); }public DragEdge getDragEdge() {return mDragEdge; }public int getDragDistance() {return mDragDistance; }public ShowMode getShowMode() {return mShowMode; }public ViewGroup getSurfaceView() {return (ViewGroup) getChildAt(1); }public ViewGroup getBottomView() {return (ViewGroup) getChildAt(0); }public enum Status {Middle, Open, Close }/** * 获取当前的开启状态 * * @return */ public Status getOpenStatus() {int surfaceLeft = getSurfaceView().getLeft(); int surfaceTop = getSurfaceView().getTop(); if (surfaceLeft == getPaddingLeft() && surfaceTop == getPaddingTop())return Status.Close; if (surfaceLeft == (getPaddingLeft() - mDragDistance)|| surfaceLeft == (getPaddingLeft() + mDragDistance)|| surfaceTop == (getPaddingTop() - mDragDistance)|| surfaceTop == (getPaddingTop() + mDragDistance))return Status.Open; return Status.Middle; }/** * 执行前布局的释放过程 * * @param xvel * @param yvel */ private void processSurfaceRelease(float xvel, float yvel) {if (xvel == 0 && getOpenStatus() == Status.Middle)close(); if (mDragEdge == DragEdge.Left || mDragEdge == DragEdge.Right) {if (xvel > 0) {if (mDragEdge == DragEdge.Left)open(); else close(); }if (xvel < 0) {if (mDragEdge == DragEdge.Left)close(); else open(); }} else {if (yvel > 0) {if (mDragEdge == DragEdge.Top)open(); else close(); }if (yvel < 0) {if (mDragEdge == DragEdge.Top)close(); else open(); }}}/** * 执行PullOut模式下,底布局的释放过程 * * @param xvel * @param yvel */ private void processBottomPullOutRelease(float xvel, float yvel) {if (xvel == 0 && getOpenStatus() == Status.Middle)close(); if (mDragEdge == DragEdge.Left || mDragEdge == DragEdge.Right) {if (xvel > 0) {if (mDragEdge == DragEdge.Left)open(); else close(); }if (xvel < 0) {if (mDragEdge == DragEdge.Left)close(); else open(); }} else {if (yvel > 0) {if (mDragEdge == DragEdge.Top)open(); else close(); }if (yvel < 0) {if (mDragEdge == DragEdge.Top)close(); else open(); }}}/** * 执行LayDown模式下,底布局的释放过程 * * @param xvel * @param yvel */ private void processBottomLayDownMode(float xvel, float yvel) {if (xvel == 0 && getOpenStatus() == Status.Middle)close(); int l = getPaddingLeft(), t = getPaddingTop(); if (xvel < 0 && mDragEdge == DragEdge.Right)l -= mDragDistance; if (xvel > 0 && mDragEdge == DragEdge.Left)l += mDragDistance; if (yvel > 0 && mDragEdge == DragEdge.Top)t += mDragDistance; if (yvel < 0 && mDragEdge == DragEdge.Bottom)t -= mDragDistance; mDragHelper.smoothSlideViewTo(getSurfaceView(), l, t); invalidate(); }public void open() {open(true, true); }public void open(boolean smooth) {open(smooth, true); }public void open(boolean smooth, boolean notify) {ViewGroup surface = getSurfaceView(), bottom = getBottomView(); int dx, dy; Rect rect = computeSurfaceLayoutArea(true); if (smooth) {mDragHelper .smoothSlideViewTo(getSurfaceView(), rect.left, rect.top); } else {dx = rect.left - surface.getLeft(); dy = rect.top - surface.getTop(); surface.layout(rect.left, rect.top, rect.right, rect.bottom); if (getShowMode() == ShowMode.PullOut) {Rect bRect = computeBottomLayoutAreaViaSurface(ShowMode.PullOut, rect); bottom.layout(bRect.left, bRect.top, bRect.right, bRect.bottom); }if (notify) {dispatchSwipeEvent(rect.left, rect.top, dx, dy); } else {safeBottomView(); }}invalidate(); }public void close() {close(true, true); }public void close(boolean smooth) {close(smooth, true); }public void close(boolean smooth, boolean notify) {ViewGroup surface = getSurfaceView(); int dx, dy; if (smooth)mDragHelper.smoothSlideViewTo(getSurfaceView(), getPaddingLeft(), getPaddingTop()); else {Rect rect = computeSurfaceLayoutArea(false); dx = rect.left - surface.getLeft(); dy = rect.top - surface.getTop(); surface.layout(rect.left, rect.top, rect.right, rect.bottom); if (notify) {dispatchSwipeEvent(rect.left, rect.top, dx, dy); } else {safeBottomView(); }}invalidate(); }public void toggle() {toggle(true); }public void toggle(boolean smooth) {if (getOpenStatus() == Status.Open)close(smooth); else if (getOpenStatus() == Status.Close)open(smooth); }private Rect computeSurfaceLayoutArea(boolean open) {int l = getPaddingLeft(), t = getPaddingTop(); if (open) {if (mDragEdge == DragEdge.Left)l = getPaddingLeft() + mDragDistance; else if (mDragEdge == DragEdge.Right)l = getPaddingLeft() - mDragDistance; else if (mDragEdge == DragEdge.Top)t = getPaddingTop() + mDragDistance; else t = getPaddingTop() - mDragDistance; }return new Rect(l, t, l + getMeasuredWidth(), t + getMeasuredHeight()); }private Rect computeBottomLayoutAreaViaSurface(ShowMode mode, Rect surfaceArea) {Rect rect = surfaceArea; int bl = rect.left, bt = rect.top, br = rect.right, bb = rect.bottom; if (mode == ShowMode.PullOut) {if (mDragEdge == DragEdge.Left)bl = rect.left - mDragDistance; else if (mDragEdge == DragEdge.Right)bl = rect.right; else if (mDragEdge == DragEdge.Top)bt = rect.top - mDragDistance; else bt = rect.bottom; if (mDragEdge == DragEdge.Left || mDragEdge == DragEdge.Right) {bb = rect.bottom; br = bl + getBottomView().getMeasuredWidth(); } else {bb = bt + getBottomView().getMeasuredHeight(); br = rect.right; }} else if (mode == ShowMode.LayDown) {if (mDragEdge == DragEdge.Left)br = bl + mDragDistance; else if (mDragEdge == DragEdge.Right)bl = br - mDragDistance; else if (mDragEdge == DragEdge.Top)bb = bt + mDragDistance; else bt = bb - mDragDistance; }return new Rect(bl, bt, br, bb); }private Rect computeBottomLayDown(DragEdge dragEdge) {int bl = getPaddingLeft(), bt = getPaddingTop(); int br, bb; if (dragEdge == DragEdge.Right) {bl = getMeasuredWidth() - mDragDistance; } else if (dragEdge == DragEdge.Bottom) {bt = getMeasuredHeight() - mDragDistance; }if (dragEdge == DragEdge.Left || dragEdge == DragEdge.Right) {br = bl + mDragDistance; bb = bt + getMeasuredHeight(); } else {br = bl + getMeasuredWidth(); bb = bt + mDragDistance; }return new Rect(bl, bt, br, bb); }public void addSwipeListener(SwipeListener l) {if (swipeListeners.size() == 2) {swipeListeners.remove(1); }swipeListeners.add(l); }public void removeSwipeListener(SwipeListener l) {swipeListeners.remove(l); }public void addOnLayoutListener(OnSwipeLayoutListener l) {if (mOnLayoutListeners == null)mOnLayoutListeners = new ArrayList<OnSwipeLayoutListener>(); mOnLayoutListeners.add(l); }public void removeOnLayoutListener(OnSwipeLayoutListener l) {if (mOnLayoutListeners != null)mOnLayoutListeners.remove(l); }private int dp2px(float dp) {return (int) (dp * getContext().getResources().getDisplayMetrics().density + 0.5f); } }
通过swipeItem.addSwipeListener()可以给滑动item添加各种事件监听,推荐使用SimpleSwipeListener的匿名类,这样就可以只重写自己关心的事件,onOpen和onClose是打开关闭的时候调用,onStartXX则是在动作一开始就调用,因此,如果需要改变后面布局的状态,请在onStartXX的时候调用,onHandRelease()则是在用户手指离开屏幕的时候调用,参数layout是事件发生的ZSwipeItem对象、xvel和yvel则是手势放开瞬间,x和y方向的加速度。onUpdate()在滑动的时候一直会调用,leftOffset和topOffset是距离左上角坐标的距离。
接下来我们就来写我们最终的组合控件:XYListView
package com.zy.zlistview.view; import java.text.SimpleDateFormat; import java.util.Date; import android.annotation.SuppressLint; import android.content.Context; import android.content.SharedPreferences; import android.content.SharedPreferences.Editor; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; import android.view.ViewTreeObserver.OnGlobalLayoutListener; import android.view.animation.DecelerateInterpolator; import android.widget.ListAdapter; import android.widget.ListView; import android.widget.RelativeLayout; import android.widget.Scroller; import com.socks.zlistview.R; public class ZListView extends ListView {private final static int SCROLLBACK_HEADER = 0; private final static int SCROLLBACK_FOOTER = 1; // 滑动时长 private final static int SCROLL_DURATION = 400; // 加载更多的距离 private final static int PULL_LOAD_MORE_DELTA = 100; // 滑动比例 private final static float OFFSET_RADIO = 2f; // 记录按下点的y坐标 private float lastY; // 用来回滚 private Scroller scroller; private IXListViewListener mListViewListener; private ZListViewHeader headerView; private RelativeLayout headerViewContent; // header的高度 private int headerHeight; // 是否能够刷新 private boolean enableRefresh = true; // 是否正在刷新 private boolean isRefreashing = false; // footer private ZListViewFooter footerView; // 是否可以加载更多 private boolean enableLoadMore; // 是否正在加载 private boolean isLoadingMore; // 是否footer准备状态 private boolean isFooterAdd = false; // item的总数 private int totalItemCount; // 记录是从header还是footer返回 private int mScrollBack; SharedPreferences sp=null; public ZListView(Context context) {super(context); initView(context); }public ZListView(Context context, AttributeSet attrs) {super(context, attrs); initView(context); }public ZListView(Context context, AttributeSet attrs, int defStyle) {super(context, attrs, defStyle); initView(context); }private void initView(Context context) {sp=getContext().getSharedPreferences("isResh", 0); scroller = new Scroller(context, new DecelerateInterpolator()); headerView = new ZListViewHeader(context); footerView = new ZListViewFooter(context); headerViewContent = (RelativeLayout) headerView.findViewById(R.id.xlistview_header_content); headerView.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {@SuppressWarnings("deprecation")@Overridepublic void onGlobalLayout() {headerHeight = headerViewContent.getHeight(); getViewTreeObserver().removeGlobalOnLayoutListener(this); }}); addHeaderView(headerView); }@Overridepublic void setAdapter(ListAdapter adapter) { // 确保footer最后添加并且只添加一次 if (isFooterAdd == false) {isFooterAdd = true; addFooterView(footerView); }super.setAdapter(adapter); }@SuppressLint({ "SimpleDateFormat", "ClickableViewAccessibility" }) @Overridepublic boolean onTouchEvent(MotionEvent ev) {totalItemCount = getAdapter().getCount(); switch (ev.getAction()) {case MotionEvent.ACTION_DOWN: // 记录按下的坐标 lastY = ev.getRawY(); break; case MotionEvent.ACTION_MOVE: // 计算移动距离 float deltaY = ev.getRawY() - lastY; lastY = ev.getRawY(); // 是第一项并且标题已经显示或者是在下拉 if (getFirstVisiblePosition() == 0 && (headerView.getVisiableHeight() > 0 || deltaY > 0)) {updateHeaderHeight(deltaY / OFFSET_RADIO); } else if (getLastVisiblePosition() == totalItemCount - 1 && (footerView.getBottomMargin() > 0 || deltaY < 0)) {updateFooterHeight(-deltaY / OFFSET_RADIO); }break; case MotionEvent.ACTION_UP: if (getFirstVisiblePosition() == 0) {if (enableRefresh&& headerView.getVisiableHeight() > headerHeight) {isRefreashing = true; Editor editor=sp.edit(); editor.putBoolean("isresh", true); SimpleDateFormat format = new SimpleDateFormat("yyyy年MM月dd日 HH时mm分ss秒"); String date = format.format(new Date()); editor.putString("date", date); editor.commit(); headerView.setState(ZListViewHeader.STATE_REFRESHING); if (mListViewListener != null) {mListViewListener.onRefresh(); }}resetHeaderHeight(); } else if (getLastVisiblePosition() == totalItemCount - 1) {if (enableLoadMore&& footerView.getBottomMargin() > PULL_LOAD_MORE_DELTA) {startLoadMore(); }resetFooterHeight(); }break; }return super.onTouchEvent(ev); }@Overridepublic void computeScroll() {// 松手之后调用 if (scroller.computeScrollOffset()) {if (mScrollBack == SCROLLBACK_HEADER) {headerView.setVisiableHeight(scroller.getCurrY()); } else {footerView.setBottomMargin(scroller.getCurrY()); }postInvalidate(); }super.computeScroll(); }public void setPullRefreshEnable(boolean enable) {enableRefresh = enable; if (!enableRefresh) {headerView.hide(); } else {headerView.show(); }}public void setPullLoadEnable(boolean enable) {enableLoadMore = enable; if (!enableLoadMore) {footerView.hide(); footerView.setOnClickListener(null); } else {isLoadingMore = false; footerView.show(); footerView.setState(ZListViewFooter.STATE_NORMAL); footerView.setOnClickListener(new OnClickListener() {@Overridepublic void onClick(View v) {startLoadMore(); }}); }}public void stopRefresh() {if (isRefreashing == true) {isRefreashing = false; resetHeaderHeight(); }}public void stopLoadMore() {if (isLoadingMore == true) {isLoadingMore = false; footerView.setState(ZListViewFooter.STATE_NORMAL); }}private void updateHeaderHeight(float delta) {headerView.setVisiableHeight((int) delta+ headerView.getVisiableHeight()); // 未处于刷新状态,更新箭头 if (enableRefresh && !isRefreashing) {if (headerView.getVisiableHeight() > headerHeight) {headerView.setState(ZListViewHeader.STATE_READY); } else {headerView.setState(ZListViewHeader.STATE_NORMAL); }}}private void resetHeaderHeight() { // 当前的可见高度 int height = headerView.getVisiableHeight(); // 如果正在刷新并且高度没有完全展示 if ((isRefreashing && height <= headerHeight) || (height == 0)) {return; } // 默认会回滚到header的位置 int finalHeight = 0; // 如果是正在刷新状态,则回滚到header的高度 if (isRefreashing && height > headerHeight) {finalHeight = headerHeight; }mScrollBack = SCROLLBACK_HEADER; // 回滚到指定位置 scroller.startScroll(0, height, 0, finalHeight - height, SCROLL_DURATION); // 触发computeScroll invalidate(); }private void updateFooterHeight(float delta) {int height = footerView.getBottomMargin() + (int) delta; if (enableLoadMore && !isLoadingMore) {if (height > PULL_LOAD_MORE_DELTA) {footerView.setState(ZListViewFooter.STATE_READY); } else {footerView.setState(ZListViewFooter.STATE_NORMAL); }}footerView.setBottomMargin(height); }private void resetFooterHeight() {int bottomMargin = footerView.getBottomMargin(); if (bottomMargin > 0) {mScrollBack = SCROLLBACK_FOOTER; scroller.startScroll(0, bottomMargin, 0, -bottomMargin, SCROLL_DURATION); invalidate(); }}private void startLoadMore() {isLoadingMore = true; footerView.setState(ZListViewFooter.STATE_LOADING); if (mListViewListener != null) {mListViewListener.onLoadMore(); }}public void setXListViewListener(IXListViewListener l) {mListViewListener = l; }public interface IXListViewListener {public void onRefresh(); public void onLoadMore(); } }
基本我们的控件就写完了,接下来就是适配器的书写了,其实和我们平常写的没有什么两样,然后我们就来实现它
package com.zy.zlistview.adapter; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Set; import android.util.Log; import android.view.View; import android.view.ViewGroup; import android.widget.BaseAdapter; import com.zy.zlistview.enums.Mode; import com.zy.zlistview.listener.SwipeListener; import com.zy.zlistview.view.ZSwipeItem; public abstract class BaseSwipeAdapter extends BaseAdapter {public static final String TAG = "BaseSwipeAdapter"; public final int INVALID_POSITION = -1; /** * 显示模式,默认单开 */ private Mode mode = Mode.Single; /** * 当前打开的item的position */ protected int openPosition = INVALID_POSITION; /** * 当前打开的所有item的position */ protected Set<Integer> openPositions = new HashSet<Integer>(); /** * 当前打开的所有ZSwipeItem对象 */ protected Set<ZSwipeItem> mShownLayouts = new HashSet<ZSwipeItem>(); public abstract int getSwipeLayoutResourceId(int position); public abstract View generateView(int position, ViewGroup parent); public abstract void fillValues(int position, View convertView); @Overridepublic final View getView(int position, View convertView, ViewGroup parent) {if (convertView == null) {convertView = generateView(position, parent); initialize(convertView, position); } else {updateConvertView(convertView, position); }fillValues(position, convertView); return convertView; }/** * 初始化item布局调用 * * @param target * @param position */ public void initialize(View target, int position) {int resId = getSwipeLayoutResourceId(position); OnLayoutListener onLayoutListener = new OnLayoutListener(position); ZSwipeItem swipeLayout = (ZSwipeItem) target.findViewById(resId); if (swipeLayout == null)throw new IllegalStateException("can not find SwipeLayout in target view"); SwipeMemory swipeMemory = new SwipeMemory(position); // 添加滑动监听器 swipeLayout.addSwipeListener(swipeMemory); // 添加布局监听器 swipeLayout.addOnLayoutListener(onLayoutListener); swipeLayout.setTag(resId, new ValueBox(position, swipeMemory, onLayoutListener)); mShownLayouts.add(swipeLayout); }/** * 复用item布局的时候调用 * * @param target * @param position */ public void updateConvertView(View target, int position) {int resId = getSwipeLayoutResourceId(position); ZSwipeItem swipeLayout = (ZSwipeItem) target.findViewById(resId); if (swipeLayout == null)throw new IllegalStateException("can not find SwipeLayout in target view"); ValueBox valueBox = (ValueBox) swipeLayout.getTag(resId); valueBox.swipeMemory.setPosition(position); valueBox.onLayoutListener.setPosition(position); valueBox.position = position; Log.d(TAG, "updateConvertView=" + position); }private void closeAllExcept(ZSwipeItem layout) {for (ZSwipeItem s : mShownLayouts) {if (s != layout)s.close(); }}/** * 获取打开的所有的item的position信息 * * @return */ public List<Integer> getOpenItems() {if (mode == Mode.Multiple) {return new ArrayList<Integer>(openPositions); } else {return Arrays.asList(openPosition); }}/** * position位置的item是否打开 * * @param position * @return */ public boolean isOpen(int position) {if (mode == Mode.Multiple) {return openPositions.contains(position); } else {return openPosition == position; }}public Mode getMode() {return mode; }public void setMode(Mode mode) {this.mode = mode; openPositions.clear(); mShownLayouts.clear(); openPosition = INVALID_POSITION; }class ValueBox {OnLayoutListener onLayoutListener; SwipeMemory swipeMemory; int position; ValueBox(int position, SwipeMemory swipeMemory, OnLayoutListener onLayoutListener) {this.swipeMemory = swipeMemory; this.onLayoutListener = onLayoutListener; this.position = position; }}public interface OnSwipeLayoutListener {public void onLayout(ZSwipeItem v); }class OnLayoutListener implements OnSwipeLayoutListener {private int position; OnLayoutListener(int position) {this.position = position; }public void setPosition(int position) {this.position = position; }@Overridepublic void onLayout(ZSwipeItem v) {if (isOpen(position)) {v.open(false, false); } else {v.close(false, false); }}}class SwipeMemory implements SwipeListener {private int position; SwipeMemory(int position) {this.position = position; }@Overridepublic void onClose(ZSwipeItem layout) {if (mode == Mode.Multiple) {openPositions.remove(position); } else {openPosition = INVALID_POSITION; }}@Overridepublic void onStartOpen(ZSwipeItem layout) {if (mode == Mode.Single) {closeAllExcept(layout); }}@Overridepublic void onOpen(ZSwipeItem layout) {if (mode == Mode.Multiple)openPositions.add(position); else {closeAllExcept(layout); openPosition = position; }}public void setPosition(int position) {this.position = position; }@Overridepublic void onStartClose(ZSwipeItem layout) { // TODO Auto-generated method stub }@Override public void onUpdate(ZSwipeItem layout, int leftOffset, int topOffset) { // TODO Auto-generated method stub }@Override public void onHandRelease(ZSwipeItem layout, float xvel, float yvel) { // TODO Auto-generated method stub }} }
最后就是初始化数据以及上拉和下拉数据的加载了,直接看代码不多说:
package com.zy.zlistview; import java.util.LinkedList; import android.app.Activity; import android.os.AsyncTask; import android.os.Bundle; import android.os.Handler; import android.view.View; import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; import android.widget.AdapterView.OnItemLongClickListener; import android.widget.Toast; import com.socks.zlistview.R; import com.zy.zlistview.view.ZListView; import com.zy.zlistview.view.ZListView.IXListViewListener; public class MainActivity extends Activity {protected static final String TAG = "MainActivity"; private ZListView listView; private Handler handler = new Handler(); private LinkedList<String> lists = new LinkedList<String>(); private ListViewAdapter mAdapter; @Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); listView = (ZListView) findViewById(R.id.listview); initData(); listView.setXListViewListener(new IXListViewListener() {@Overridepublic void onRefresh() {handler.postDelayed(new Runnable() {@Overridepublic void run() {PullRefreshTask rTask = new PullRefreshTask(); rTask.execute(1000); }}, 1000); }@Overridepublic void onLoadMore() {handler.postDelayed(new Runnable() {@Overridepublic void run() {UpRefreshTask rTask = new UpRefreshTask(); rTask.execute(1000); }}, 1000); }}); listView.setPullLoadEnable(true); listView.setOnItemClickListener(new OnItemClickListener() {@Overridepublic void onItemClick(AdapterView> parent, View view, int position, long id) {Toast.makeText(MainActivity.this, "onItemClick=" + position, Toast.LENGTH_SHORT).show(); }}); listView.setOnItemLongClickListener(new OnItemLongClickListener() {@Overridepublic boolean onItemLongClick(AdapterView> parent, View view, int position, long id) {Toast.makeText(MainActivity.this, "onItemLongClick=" + position, Toast.LENGTH_SHORT).show(); return true; }}); mAdapter = new ListViewAdapter(this, lists); listView.setAdapter(mAdapter); }private void initData() {for (int i = 0; i < 20; i++) {lists.add("我是测试数据" + i); }}// AsyncTask异步任务加载数据 class PullRefreshTask extends AsyncTask<Integer, Integer, String> {@Overrideprotected String doInBackground(Integer... params) {try {Thread.sleep(params[0]); } catch (Exception e) {e.printStackTrace(); } // 在data最前添加数据 for (int j = 0; j < 5; j++) {lists.addFirst("下拉刷新加载的数据" + j); }return null; }@Overrideprotected void onPostExecute(String result) {super.onPostExecute(result); mAdapter.notifyDataSetChanged();//数据更新 listView.stopRefresh();//头部状态重置 }}// AsyncTask异步任务加载数据 class UpRefreshTask extends AsyncTask<Integer, Integer, String> {@Overrideprotected String doInBackground(Integer... params) {try {Thread.sleep(params[0]); } catch (Exception e) {e.printStackTrace(); } // 在data最前添加数据 for (int j = 0; j < 5; j++) {lists.addLast("上拉加载的数据" + j); }return null; }@Override protected void onPostExecute(String result) {super.onPostExecute(result); mAdapter.notifyDataSetChanged();//数据更新 listView.stopLoadMore();//底部状态重置 }} }
效果图请看:效果还是蛮不错的
里面的难点就是SwipeLayout里面奋力出来的,那里面条目是不只有删除功能还有打开功能或其他,所以就是条目的滑动是最难理解,因此,当我们的item被滑动的时候,就会不断的调用这个onLayout方法,我们判断当前打开的position,然后恢复现场即可。然后项目里不再是我们常见的scrollListener,看代码才知道ViewTreeObserver里面实现了OnScrollChangedListener,smooth代表是否是平滑移动的,如果是的话,就调用了ViewDragHelper.smoothSlideViewTo()。其实在ViewDragHelper里面有一个Scroller,这个方法就是通过Scroller类来实现的,但是只这样写还不行,我们还需要重写computeScroll(),然后用下面的代码,让滚动一直持续下去,否则View是不会滚动起来的。如果不是smooth的话,就直接layout(),把View的位置定位过去了。这里面的item的实现参考了大神凯子哥(http://blog.csdn.net/zhaokaiqiang1992/article/details)的项目,然后里面的一些bug进行了修复,但是发现还是有bug的,虽然左右滑动条目然后listView上下滚动的冲突修复了,但是还是感觉有些小问题,但是绝对是可以直接项目里使用。
源码下载:点击打开链接
醉了,要睡觉了。。。
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
