大家对这些功能都是看的多了,然后对上拉刷新和下拉加载的原理都是非常清楚的,所以实现这功能其实也就是为了让大家能够从众多的同行们来进行比较学习而已,虽然即使是这样,但是面试的时候面试官还是会问你上拉和下拉是怎么实现的,滑动删除功能是怎么实现,其实要实现这些功能又不是唯一的方法,但是基本上思想都是一致的。然后githup上的这些例子是非常的多,然后实现的也是大同小异但是也不能不让我们去球童存异。作为天朝的程序员即使是一个伸手党也不必太觉得羞耻,能把别人的东西来改一改或者沿用别人的思想来模仿也是不错的。然后公司的项目也可能不建议直接去导入XlistView来实现下拉的功能,所以我们可能就需要自己去实现一些这样的功能了,废话不多说,稍微就介绍下原理,主要还是代码为主,即使看到代码和别人大同小异也不要计较,因为这些功能纯粹就是模仿好了,当然参考别人的代码也不是什么丢脸的事,关键的是对自己有帮助就好了啊。
我们要写这样一个功能当然的先从自定义的listView开始了,说起来其实就是一个组合控件,它就由3部分组成:head 、content、footer,所以我们的代码也就可以分开来实现了额。先我们写头部的布局和功能。
xlistview_header.xml
<?xmlversion="1.0"encoding="utf-8"?><LinearLayout xmlns:android="/apk/res/android"xmlns:tools="/tools"android:layout_width="match_parent"android:layout_height="wrap_content"android:gravity="bottom"><RelativeLayoutandroid:id="@+id/xlistview_header_content"android:layout_width="match_parent"android:layout_height="60dp"android:layout_weight="1"tools:ignore="UselessParent"><LinearLayoutandroid: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"><TextViewandroid: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"/><!-- 最近更新 --><TextViewandroid:id="@+id/head_lastUpdatedTextView"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginTop="5dp"android:text="上次更新:"android:textSize="12dp"/></LinearLayout><ImageViewandroid: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"/><ProgressBarandroid: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(头部布局)
packagecom.zy.zlistview.widget;importandroid.content.Context;importandroid.content.SharedPreferences;importandroid.util.AttributeSet;importandroid.view.LayoutInflater;importandroid.view.View;importandroid.view.animation.Animation;importandroid.view.animation.RotateAnimation;importandroid.widget.ImageView;importandroid.widget.LinearLayout;importandroid.widget.ProgressBar;importandroid.widget.TextView;importcom.socks.zlistview.R;public classZListViewHeaderextendsLinearLayout {//手势动作变化,状态的三种描述private static finalString HINT_NORMAL ="下拉刷新";private static finalString HINT_READY ="松开刷新数据";private static finalString HINT_LOADING ="正在加载..."; // 正常状态public final static intSTATE_NORMAL = 0; // 准备刷新状态,也就是箭头方向发生改变之后的状态public final static intSTATE_READY = 1; // 刷新状态,箭头变成了progressBarpublic final static intSTATE_REFRESHING = 2; // 布局容器,也就是根布局privateLinearLayout container; // 箭头图片privateImageView mArrowImageView; // 刷新状态显示privateProgressBar mProgressBar; // 说明文本privateTextView mHintTextView; // 记录当前的状态private intmState; // 用于改变箭头的方向的动画privateAnimation mRotateUpAnim;privateAnimation mRotateDownAnim; // 动画持续时间private final intROTATE_ANIM_DURATION = 180; //x显示上次刷新的时间privateTextView head_lastUpdatedTextView;protectedTextView getHead_lastUpdatedTextView() {returnhead_lastUpdatedTextView; }public voidsetHead_lastUpdatedTextView(TextView head_lastUpdatedTextView) {this.head_lastUpdatedTextView = head_lastUpdatedTextView; }publicZListViewHeader(Context context) {super(context); initView(context); }publicZListViewHeader(Context context, AttributeSet attrs) {super(context, attrs); initView(context); }private voidinitView(Context context) {mState = STATE_NORMAL;// 初始情况下,设置下拉刷新view高度为0LinearLayout.LayoutParams lp =newLinearLayout.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 =newRotateAnimation(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 =newRotateAnimation(-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 voidsetState(intstate) {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) {caseSTATE_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;caseSTATE_READY:if(mState != STATE_READY) {mArrowImageView.clearAnimation();mArrowImageView.startAnimation(mRotateUpAnim);mHintTextView.setText(HINT_READY);//显示提示文字 }break;caseSTATE_REFRESHING:mHintTextView.setText(HINT_LOADING);break; }mState = state; }public voidsetVisiableHeight(intheight) {//设置头部可视高度if(height < 0)height = 0; LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) container.getLayoutParams(); lp.height = height; container.setLayoutParams(lp); }public intgetVisiableHeight() {returncontainer.getHeight(); }public voidshow() {container.setVisibility(View.VISIBLE); }public voidhide() {container.setVisibility(View.INVISIBLE); }}
接下来看底部:xlistview_footer.xml
<?xmlversion="1.0"encoding="utf-8"?><LinearLayout xmlns:android="/apk/res/android"xmlns:tools="/tools"android:layout_width="fill_parent"android:layout_height="wrap_content"><RelativeLayoutandroid:id="@+id/xlistview_footer_content"android:layout_width="fill_parent"android:layout_height="wrap_content"android:padding="5dp"tools:ignore="UselessParent"><LinearLayoutandroid:id="@+id/footer_load"android:layout_width="fill_parent"android:layout_height="wrap_content"android:gravity="center_horizontal"android:visibility="invisible"><ProgressBarandroid:id="@+id/xlistview_footer_progressbar"android:layout_width="30dp"android:layout_height="30dp"android:layout_gravity="center"android:visibility="invisible"/><TextViewandroid: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><TextViewandroid: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
packagecom.zy.zlistview.view;importandroid.content.Context;importandroid.util.AttributeSet;importandroid.view.LayoutInflater;importandroid.view.View;importandroid.widget.LinearLayout;importandroid.widget.TextView;importcom.socks.zlistview.R;public classZListViewFooterextendsLinearLayout{public final staticStringHINT_READ="松开载入更多";public final staticStringHINT_NORMAL="查看更多"; // 正常状态public final static intSTATE_NORMAL= 0; // 准备状态public final static intSTATE_READY= 1; // 加载状态public final static intSTATE_LOADING= 2;privateViewmContentView;privateViewmProgressBar;privateTextViewmHintView;privateLinearLayoutfooter_load;privateTextViewloding_textview;publicZListViewFooter(Context context) {super(context); initView(context); }publicZListViewFooter(Context context, AttributeSet attrs) {super(context, attrs); initView(context); }private voidinitView(Context context) {LinearLayout moreView = (LinearLayout) LayoutInflater.from(context).inflate(R.layout.xlistview_footer,null); addView(moreView); moreView.setLayoutParams(newLinearLayout.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); }/** * 设置当前的状态 * *@paramstate */public voidsetState(intstate) {//mProgressBar.setVisibility(View.INVISIBLE);footer_load.setVisibility(View.INVISIBLE);mHintView.setVisibility(View.INVISIBLE);switch(state) {caseSTATE_READY://准备状态mHintView.setVisibility(View.VISIBLE);mHintView.setText(HINT_READ);break;caseSTATE_NORMAL://正常状态mHintView.setVisibility(View.VISIBLE);mHintView.setText(HINT_NORMAL);break;caseSTATE_LOADING:footer_load.setVisibility(View.VISIBLE);mProgressBar.setVisibility(View.VISIBLE);loding_textview.setVisibility(View.VISIBLE);loding_textview.setText("正在加载中....");break; }}public voidsetBottomMargin(intheight) {if(height > 0) {LinearLayout.LayoutParamslp = (LinearLayout.LayoutParams)mContentView.getLayoutParams(); lp.bottomMargin= height;mContentView.setLayoutParams(lp); }}public intgetBottomMargin() {LinearLayout.LayoutParamslp = (LinearLayout.LayoutParams)mContentView.getLayoutParams();returnlp.bottomMargin; }public voidhide() {LinearLayout.LayoutParamslp = (LinearLayout.LayoutParams)mContentView.getLayoutParams(); lp.height= 0;mContentView.setLayoutParams(lp); }public voidshow() {LinearLayout.LayoutParamslp = (LinearLayout.LayoutParams)mContentView.getLayoutParams(); lp.height=LayoutParams.WRAP_CONTENT;mContentView.setLayoutParams(lp); }}
跟实现头部的代码差不多,主要就是一些状态改变然后布局做相应的改变。
接下来是中间item的布局,然后是模仿qq的滑动删除的功能,所以我们还是参考了gitup上的开源项目SwipeXXX,你可以自己去看下,然后参考了里面的很多东西,不多说了直接代码
SwipeListener.java
packagecom.zy.zlistview.listener;importcom.zy.zlistview.view.ZSwipeItem;public interfaceSwipeListener {public voidonStartOpen(ZSwipeItem layout);public voidonOpen(ZSwipeItem layout);public voidonStartClose(ZSwipeItem layout);public voidonClose(ZSwipeItem layout);public voidonUpdate(ZSwipeItem layout,intleftOffset,inttopOffset);public voidonHandRelease(ZSwipeItem layout,floatxvel,floatyvel);}
然后我们设了几个枚举类方便我们对item的控制
public enumDragEdge{Left,Right,Top,Bottom;};public enumMode{Single,Multiple;};public enumShowMode{LayDown,PullOut;}
我们先来参考QQ条目的布局实现:
packagecom.zy.zlistview.view;importjava.util.ArrayList;importjava.util.List;importandroid.annotation.SuppressLint;importandroid.content.Context;importandroid.content.res.TypedArray;importandroid.graphics.Rect;importandroid.support.v4.view.ViewCompat;importandroid.support.v4.widget.ViewDragHelper;importandroid.util.AttributeSet;importandroid.util.Log;importandroid.view.GestureDetector;importandroid.view.MotionEvent;importandroid.view.View;importandroid.view.ViewGroup;importandroid.view.ViewParent;importandroid.widget.Adapter;importandroid.widget.AdapterView;importandroid.widget.BaseAdapter;importandroid.widget.FrameLayout;importandroid.widget.ListAdapter;importcom.socks.zlistview.R;importcom.zy.zlistview.adapter.BaseSwipeAdapter.OnSwipeLayoutListener;importcom.zy.zlistview.enums.DragEdge;importcom.zy.zlistview.enums.ShowMode;importcom.zy.zlistview.listener.SwipeListener;@SuppressLint("ClickableViewAccessibility")public classZSwipeItemextendsFrameLayout{protected static finalStringTAG="ZSwipeItem";privateViewDragHelpermDragHelper;private intmDragDistance= 0;privateDragEdgemDragEdge;privateShowModemShowMode;private floatmHorizontalSwipeOffset;private floatmVerticalSwipeOffset;private booleanmSwipeEnabled=true;privateList<OnSwipeLayoutListener>mOnLayoutListeners;privateList<SwipeListener>swipeListeners=newArrayList<SwipeListener>();publicZSwipeItem(Context context) {this(context,null); }publicZSwipeItem(Context context, AttributeSet attrs) {this(context, attrs, 0); }publicZSwipeItem(Context context, AttributeSet attrs,intdefStyle) {super(context, attrs, defStyle);mDragHelper=ViewDragHelper.create(this,mDragHelperCallback); TypedArray a = context.obtainStyledAttributes(attrs,R.styleable.ZSwipeItem);// 默认是右边缘检测intordinal = 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(); }/** * 进行拖拽的主要类 */privateViewDragHelper.CallbackmDragHelperCallback=newViewDragHelper.Callback() {/** * 计算被横向拖动view的left */@Overridepublic intclampViewPositionHorizontal(View child,intleft,intdx) {if(child == getSurfaceView()) {switch(mDragEdge) {caseTop:caseBottom:returngetPaddingLeft();caseLeft:if(left < getPaddingLeft())returngetPaddingLeft();if(left > getPaddingLeft() +mDragDistance)returngetPaddingLeft() +mDragDistance;break;caseRight:if(left > getPaddingLeft())returngetPaddingLeft();if(left < getPaddingLeft() -mDragDistance)returngetPaddingLeft() -mDragDistance;break;}}else if(child == getBottomView()) {switch(mDragEdge) {caseTop:caseBottom:returngetPaddingLeft();caseLeft:if(mShowMode== ShowMode.PullOut) {if(left > getPaddingLeft())returngetPaddingLeft(); }break;caseRight:if(mShowMode== ShowMode.PullOut) {if(left < getMeasuredWidth() -mDragDistance) {returngetMeasuredWidth() -mDragDistance; }}break;}}returnleft; }/** * 计算被纵向拖动的view的top */@Overridepublic intclampViewPositionVertical(View child,inttop,intdy) {if(child == getSurfaceView()) {switch(mDragEdge) {caseLeft:caseRight:returngetPaddingTop();caseTop:if(top < getPaddingTop())returngetPaddingTop();if(top > getPaddingTop() +mDragDistance)returngetPaddingTop() +mDragDistance;break;caseBottom:if(top < getPaddingTop() -mDragDistance) {returngetPaddingTop() -mDragDistance; }if(top > getPaddingTop()) {returngetPaddingTop(); }}}else{switch(mDragEdge) {caseLeft:caseRight:returngetPaddingTop();caseTop:if(mShowMode== ShowMode.PullOut) {if(top > getPaddingTop())returngetPaddingTop(); }else{if(getSurfaceView().getTop() + dy < getPaddingTop())returngetPaddingTop();if(getSurfaceView().getTop() + dy > getPaddingTop()+mDragDistance)returngetPaddingTop() +mDragDistance; }break;caseBottom:if(mShowMode== ShowMode.PullOut) {if(top < getMeasuredHeight() -mDragDistance)returngetMeasuredHeight() -mDragDistance; }else{if(getSurfaceView().getTop() + dy >= getPaddingTop())returngetPaddingTop();if(getSurfaceView().getTop() + dy <= getPaddingTop()-mDragDistance)returngetPaddingTop() -mDragDistance; }}}returntop; }/** * 确定要进行拖动的view */@Overridepublic booleantryCaptureView(View child,intpointerId) {returnchild == getSurfaceView() || child == getBottomView(); }/** * 确定横向拖动边界 */@Overridepublic intgetViewHorizontalDragRange(View child) {returnmDragDistance; }/** * 确定纵向拖动边界 */@Overridepublic intgetViewVerticalDragRange(Viewchild) {returnmDragDistance; }/** * 当子控件被释放的时候调用,可以获取加速度的数据,来判断用户意图 */@Overridepublic voidonViewReleased(ViewreleasedChild,floatxvel,floatyvel) {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的位置跟随手指移动 */@Overridepublic voidonViewPositionChanged(ViewchangedView,intleft,inttop,intdx,intdy) {intevLeft = getSurfaceView().getLeft();intevTop = 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);intnewLeft = getSurfaceView().getLeft() + dx;intnewTop = 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事件分发器 * *@paramsurfaceLeft *@paramsurfaceTop *@paramdx *@paramdy */protected voiddispatchSwipeEvent(intsurfaceLeft,intsurfaceTop,intdx,intdy) {DragEdge edge = getDragEdge();booleanopen =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 intmEventCounter= 0; /** * swipe事件分发器 * *@paramsurfaceLeft *@paramsurfaceTop *@paramopen */protected voiddispatchSwipeEvent(intsurfaceLeft,intsurfaceTop,booleanopen) {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 voidsafeBottomView() {Statusstatus = getOpenStatus(); ViewGroup bottom = getBottomView();if(status ==Status.Close) {if(bottom.getVisibility() !=INVISIBLE)bottom.setVisibility(INVISIBLE); }else{if(bottom.getVisibility() !=VISIBLE)bottom.setVisibility(VISIBLE); }}@Overridepublic voidcomputeScroll() {super.computeScroll();// 让滚动一直进行下去if(mDragHelper.continueSettling(true)) {ViewCompat.postInvalidateOnAnimation(this); }}/** * 强制布局中必须嵌套两个ViewGroup布局,在新item出现的时候就会调用 */ @SuppressLint("WrongCall")@Overrideprotected voidonLayout(booleanchanged,intl,intt,intr,intb) {intchildCount = getChildCount();if(childCount != 2) {throw newIllegalStateException("You need 2 views in SwipeLayout"); }if(!(getChildAt(0)instanceofViewGroup)|| !(getChildAt(1)instanceofViewGroup)) {throw newIllegalArgumentException("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(inti = 0; i <mOnLayoutListeners.size(); i++) {mOnLayoutListeners.get(i).onLayout(this); }}private voidlayoutPullOut() {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 voidlayoutLayDown() {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()); }@Overrideprotected voidonMeasure(intwidthMeasureSpec,intheightMeasureSpec) {super.onMeasure(widthMeasureSpec, heightMeasureSpec);// 初始化移动距离if(mDragEdge== DragEdge.Left ||mDragEdge== DragEdge.Right)mDragDistance= getBottomView().getMeasuredWidth()- dp2px(mHorizontalSwipeOffset);else{mDragDistance= getBottomView().getMeasuredHeight()- dp2px(mVerticalSwipeOffset); }}private booleanmTouchConsumedByChild=false; @Overridepublic booleanonInterceptTouchEvent(MotionEvent ev) {if(!isEnabled() || !isEnabledInAdapterView()) {return true; }if(!isSwipeEnabled()) {return false; }intaction = ev.getActionMasked();switch(action) {caseMotionEvent.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;caseMotionEvent.ACTION_UP:caseMotionEvent.ACTION_CANCEL:mTouchConsumedByChild=false; }if(mTouchConsumedByChild)return false;returnmDragHelper.shouldInterceptTouchEvent(ev); }/** * ViewGroup的子View是否需要处理这个事件 * *@paramv *@paramevent *@return*/privateView childNeedHandleTouchEvent(ViewGroup v, MotionEvent event) {if(v ==null)return null;if(v.onTouchEvent(event))returnv;intchildCount = v.getChildCount();for(inti = childCount - 1; i >= 0; i--) {View child = v.getChildAt(i);if(childinstanceofViewGroup) {View grandChild = childNeedHandleTouchEvent((ViewGroup) child, event);if(grandChild !=null)returngrandChild; }else{if(childNeedHandleTouchEvent(v.getChildAt(i), event))returnv.getChildAt(i); }}return null; }/** * 判断View是否要去处理触摸事件 * *@paramv *@paramevent *@return*/private booleanchildNeedHandleTouchEvent(View v, MotionEvent event) {if(v ==null)return false;int[] loc =new int[2]; v.getLocationOnScreen(loc);intleft = loc[0];inttop = loc[1];if(event.getRawX() > left && event.getRawX() < left + v.getWidth()&& event.getRawY() > top&& event.getRawY() < top + v.getHeight()) {returnv.onTouchEvent(event); }return false; }private floatsX= -1,sY= -1; @Overridepublic booleanonTouchEvent(MotionEventevent) {if(!isEnabledInAdapterView() || !isEnabled())return true;if(!isSwipeEnabled())return super.onTouchEvent(event);intaction = event.getActionMasked(); ViewParent parent = getParent();gestureDetector.onTouchEvent(event);Statusstatus = getOpenStatus(); ViewGroup touching =null;if(status ==Status.Close) {touching = getSurfaceView(); }else if(status ==Status.Open) {touching = getBottomView(); }switch(action) {caseMotionEvent.ACTION_DOWN:mDragHelper.processTouchEvent(event);parent.requestDisallowInterceptTouchEvent(true);sX= event.getRawX();sY= event.getRawY();if(touching !=null)touching.setPressed(true);return true;caseMotionEvent.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;}floatdistanceX = event.getRawX() -sX;floatdistanceY = event.getRawY() -sY;floatangle =Math.abs(distanceY / distanceX);angle = (float)Math.toDegrees(Math.atan(angle));booleandoNothing =false;// 根据触摸角度,判断是否执行用户操作if(mDragEdge== DragEdge.Right) {booleansuitable = (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) {booleansuitable = (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) {booleansuitable = (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) {booleansuitable = (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; }caseMotionEvent.ACTION_UP:caseMotionEvent.ACTION_CANCEL: {sX= -1;sY= -1;if(touching !=null) {touching.setPressed(false);}}default:parent.requestDisallowInterceptTouchEvent(true);mDragHelper.processTouchEvent(event); }return true; }/** * if working in {@linkandroid.widget.AdapterView}, we should response * {@linkandroid.widget.Adapter} isEnable(int position). * *@returntrue when item is enabled, else disabled. */private booleanisEnabledInAdapterView() {@SuppressWarnings("rawtypes")AdapterView adapterView = getAdapterView();booleanenable =true;if(adapterView !=null) {Adapter adapter = adapterView.getAdapter();if(adapter !=null) {intp = adapterView.getPositionForView(ZSwipeItem.this);if(adapterinstanceofBaseAdapter) {enable = ((BaseAdapter) adapter).isEnabled(p);}else if(adapterinstanceofListAdapter) {enable = ((ListAdapter) adapter).isEnabled(p);}}}returnenable; }public voidsetSwipeEnabled(booleanenabled) {mSwipeEnabled= enabled; }public booleanisSwipeEnabled() {returnmSwipeEnabled; }@SuppressWarnings("rawtypes")privateAdapterView getAdapterView() {ViewParent t = getParent();while(t !=null) {if(tinstanceofAdapterView) {return(AdapterView) t; }t = t.getParent(); }return null; }private voidperformAdapterViewItemClick(MotionEvent e) {ViewParent t = getParent();Log.d(TAG,"performAdapterViewItemClick()");while(t !=null) {if(tinstanceofAdapterView) {@SuppressWarnings("rawtypes")AdapterView view = (AdapterView) t;intp = view.getPositionForView(ZSwipeItem.this);if(p != AdapterView.INVALID_POSITION&& view.performItemClick(view.getChildAt(p - view.getFirstVisiblePosition()), p, view.getAdapter().getItemId(p)))return; }else{if(tinstanceofView && ((View) t).performClick())return; }t = t.getParent(); }}privateGestureDetectorgestureDetector=newGestureDetector(getContext(),newSwipeDetector()); /** * 手势监听器,通过调用performItemClick、performItemLongClick,来解决item的点击问题, * * */private classSwipeDetectorextendsGestureDetector.SimpleOnGestureListener{@Overridepublic booleanonDown(MotionEvente) {return true; }@Overridepublic booleanonSingleTapUp(MotionEvente) {// 当用户单击之后,手指抬起的时候调用,如果没有双击监听器,就直接调用performAdapterViewItemClick(e);return true; }@Overridepublic booleanonSingleTapConfirmed(MotionEvente) {// 这个方法只有在确认用户不会发生双击事件的时候调用return false; }@Overridepublic voidonLongPress(MotionEvent e) {// 长按事件performLongClick(); }@Overridepublic booleanonDoubleTap(MotionEvent e) {return false; }}public voidsetDragEdge(DragEdge dragEdge) {mDragEdge= dragEdge; requestLayout(); }public voidsetShowMode(ShowMode mode) {mShowMode= mode; requestLayout(); }publicDragEdge getDragEdge() {returnmDragEdge; }public intgetDragDistance() {returnmDragDistance; }publicShowMode getShowMode() {returnmShowMode; }publicViewGroup getSurfaceView() {return(ViewGroup) getChildAt(1); }publicViewGroup getBottomView() {return(ViewGroup) getChildAt(0); }public enumStatus{Middle,Open,Close}/** * 获取当前的开启状态 * *@return*/publicStatusgetOpenStatus() {intsurfaceLeft = getSurfaceView().getLeft();intsurfaceTop = getSurfaceView().getTop();if(surfaceLeft == getPaddingLeft() && surfaceTop == getPaddingTop())returnStatus.Close;if(surfaceLeft == (getPaddingLeft() -mDragDistance)|| surfaceLeft == (getPaddingLeft() +mDragDistance)|| surfaceTop == (getPaddingTop() -mDragDistance)|| surfaceTop == (getPaddingTop() +mDragDistance))returnStatus.Open;returnStatus.Middle; }/** * 执行前布局的释放过程 * *@paramxvel *@paramyvel */private voidprocessSurfaceRelease(floatxvel,floatyvel) {if(xvel == 0 && getOpenStatus() ==Status.Middle)close();if(mDragEdge== DragEdge.Left ||mDragEdge== DragEdge.Right) {if(xvel > 0) {if(mDragEdge== DragEdge.Left)open();elseclose(); }if(xvel < 0) {if(mDragEdge== DragEdge.Left)close();elseopen(); }}else{if(yvel > 0) {if(mDragEdge== DragEdge.Top)open();elseclose(); }if(yvel < 0) {if(mDragEdge== DragEdge.Top)close();elseopen(); }}}/** * 执行PullOut模式下,底布局的释放过程 * *@paramxvel *@paramyvel */private voidprocessBottomPullOutRelease(floatxvel,floatyvel) {if(xvel == 0 && getOpenStatus() ==Status.Middle)close();if(mDragEdge== DragEdge.Left ||mDragEdge== DragEdge.Right) {if(xvel > 0) {if(mDragEdge== DragEdge.Left)open();elseclose(); }if(xvel < 0) {if(mDragEdge== DragEdge.Left)close();elseopen(); }}else{if(yvel > 0) {if(mDragEdge== DragEdge.Top)open();elseclose(); }if(yvel < 0) {if(mDragEdge== DragEdge.Top)close();elseopen(); }}}/** * 执行LayDown模式下,底布局的释放过程 * *@paramxvel *@paramyvel */private voidprocessBottomLayDownMode(floatxvel,floatyvel) {if(xvel == 0 && getOpenStatus() ==Status.Middle)close();intl = 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 voidopen() {open(true,true); }public voidopen(booleansmooth) {open(smooth,true); }public voidopen(booleansmooth,booleannotify) {ViewGroup surface = getSurfaceView(), bottom = getBottomView();intdx, 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 voidclose() {close(true,true); }public voidclose(booleansmooth) {close(smooth,true); }public voidclose(booleansmooth,booleannotify) {ViewGroup surface = getSurfaceView();intdx, 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 voidtoggle() {toggle(true); }public voidtoggle(booleansmooth) {if(getOpenStatus() ==Status.Open)close(smooth);else if(getOpenStatus() ==Status.Close)open(smooth); }privateRect computeSurfaceLayoutArea(booleanopen) {intl = 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;elset = getPaddingTop() -mDragDistance; }return newRect(l, t, l + getMeasuredWidth(), t + getMeasuredHeight()); }privateRect computeBottomLayoutAreaViaSurface(ShowMode mode,RectsurfaceArea) {Rectrect = surfaceArea;intbl = 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;elsebt = 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;elsebt = bb -mDragDistance; }return newRect(bl, bt, br, bb); }privateRectcomputeBottomLayDown(DragEdge dragEdge) {intbl = getPaddingLeft(), bt = getPaddingTop();intbr, 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 newRect(bl, bt, br, bb); }public voidaddSwipeListener(SwipeListener l) {if(swipeListeners.size() == 2) {swipeListeners.remove(1); }swipeListeners.add(l); }public voidremoveSwipeListener(SwipeListener l) {swipeListeners.remove(l); }public voidaddOnLayoutListener(OnSwipeLayoutListener l) {if(mOnLayoutListeners==null)mOnLayoutListeners=newArrayList<OnSwipeLayoutListener>();mOnLayoutListeners.add(l); }public voidremoveOnLayoutListener(OnSwipeLayoutListener l) {if(mOnLayoutListeners!=null)mOnLayoutListeners.remove(l); }private intdp2px(floatdp) {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
packagecom.zy.zlistview.view;importjava.text.SimpleDateFormat;importjava.util.Date;importandroid.annotation.SuppressLint;importandroid.content.Context;importandroid.content.SharedPreferences;importandroid.content.SharedPreferences.Editor;importandroid.util.AttributeSet;importandroid.view.MotionEvent;importandroid.view.View;importandroid.view.ViewTreeObserver.OnGlobalLayoutListener;importandroid.view.animation.DecelerateInterpolator;importandroid.widget.ListAdapter;importandroid.widget.ListView;importandroid.widget.RelativeLayout;importandroid.widget.Scroller;importcom.socks.zlistview.R;public classZListViewextendsListView {private final static intSCROLLBACK_HEADER = 0;private final static intSCROLLBACK_FOOTER = 1; // 滑动时长private final static intSCROLL_DURATION = 400; // 加载更多的距离private final static intPULL_LOAD_MORE_DELTA = 100; // 滑动比例private final static floatOFFSET_RADIO = 2f; // 记录按下点的y坐标private floatlastY; // 用来回滚privateScroller scroller;privateIXListViewListener mListViewListener;privateZListViewHeader headerView;privateRelativeLayout headerViewContent; // header的高度private intheaderHeight; // 是否能够刷新private booleanenableRefresh =true; // 是否正在刷新private booleanisRefreashing =false; // footerprivateZListViewFooter footerView; // 是否可以加载更多private booleanenableLoadMore; // 是否正在加载private booleanisLoadingMore; // 是否footer准备状态private booleanisFooterAdd =false; // item的总数private inttotalItemCount; // 记录是从header还是footer返回private intmScrollBack; SharedPreferences sp=null;publicZListView(Context context) {super(context); initView(context); }publicZListView(Context context, AttributeSet attrs) {super(context, attrs); initView(context); }publicZListView(Context context, AttributeSet attrs,intdefStyle) {super(context, attrs, defStyle); initView(context); }private voidinitView(Context context) {sp=getContext().getSharedPreferences("isResh", 0); scroller =newScroller(context,newDecelerateInterpolator()); headerView =newZListViewHeader(context); footerView =newZListViewFooter(context); headerViewContent = (RelativeLayout) headerView.findViewById(R.id.xlistview_header_content); headerView.getViewTreeObserver().addOnGlobalLayoutListener(newOnGlobalLayoutListener() {@SuppressWarnings("deprecation")@Overridepublic voidonGlobalLayout() {headerHeight = headerViewContent.getHeight(); getViewTreeObserver().removeGlobalOnLayoutListener(this);}}); addHeaderView(headerView); }@Overridepublic voidsetAdapter(ListAdapter adapter) {// 确保footer最后添加并且只添加一次if(isFooterAdd ==false) {isFooterAdd =true; addFooterView(footerView); }super.setAdapter(adapter); }@SuppressLint({"SimpleDateFormat","ClickableViewAccessibility"}) @Overridepublic booleanonTouchEvent(MotionEvent ev) {totalItemCount = getAdapter().getCount();switch(ev.getAction()) {caseMotionEvent.ACTION_DOWN:// 记录按下的坐标 lastY = ev.getRawY();break;caseMotionEvent.ACTION_MOVE:// 计算移动距离floatdeltaY = 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;caseMotionEvent.ACTION_UP:if(getFirstVisiblePosition() == 0) {if(enableRefresh&& headerView.getVisiableHeight() > headerHeight) {isRefreashing =true; Editor editor=sp.edit(); editor.putBoolean("isresh",true); SimpleDateFormat format =newSimpleDateFormat("yyyy年MM月dd日 HH时mm分ss秒"); String date = format.format(newDate()); editor.putString("date", date); mit(); 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 voidcomputeScroll() {// 松手之后调用if(puteScrollOffset()) {if(mScrollBack == SCROLLBACK_HEADER) {headerView.setVisiableHeight(scroller.getCurrY()); }else{footerView.setBottomMargin(scroller.getCurrY()); }postInvalidate(); }super.computeScroll(); }public voidsetPullRefreshEnable(booleanenable) {enableRefresh = enable;if(!enableRefresh) {headerView.hide(); }else{headerView.show(); }}public voidsetPullLoadEnable(booleanenable) {enableLoadMore = enable;if(!enableLoadMore) {footerView.hide(); footerView.setOnClickListener(null); }else{isLoadingMore =false; footerView.show(); footerView.setState(ZListViewFooter.STATE_NORMAL); footerView.setOnClickListener(newOnClickListener() {@Overridepublic voidonClick(View v) {startLoadMore();}}); }}public voidstopRefresh() {if(isRefreashing ==true) {isRefreashing =false; resetHeaderHeight(); }}public voidstopLoadMore() {if(isLoadingMore ==true) {isLoadingMore =false; footerView.setState(ZListViewFooter.STATE_NORMAL); }}private voidupdateHeaderHeight(floatdelta) {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 voidresetHeaderHeight() {// 当前的可见高度intheight = headerView.getVisiableHeight();// 如果正在刷新并且高度没有完全展示if((isRefreashing && height <= headerHeight) || (height == 0)) {return; }// 默认会回滚到header的位置intfinalHeight = 0;// 如果是正在刷新状态,则回滚到header的高度if(isRefreashing && height > headerHeight) {finalHeight = headerHeight; }mScrollBack = SCROLLBACK_HEADER;// 回滚到指定位置scroller.startScroll(0, height, 0, finalHeight - height,SCROLL_DURATION);// 触发computeScrollinvalidate(); }private voidupdateFooterHeight(floatdelta) {intheight = 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 voidresetFooterHeight() {intbottomMargin = footerView.getBottomMargin();if(bottomMargin > 0) {mScrollBack = SCROLLBACK_FOOTER; scroller.startScroll(0, bottomMargin, 0, -bottomMargin,SCROLL_DURATION); invalidate(); }}private voidstartLoadMore() {isLoadingMore =true; footerView.setState(ZListViewFooter.STATE_LOADING);if(mListViewListener !=null) {mListViewListener.onLoadMore(); }}public voidsetXListViewListener(IXListViewListener l) {mListViewListener= l; }public interfaceIXListViewListener {public voidonRefresh();public voidonLoadMore(); }}
基本我们的控件就写完了,接下来就是适配器的书写了,其实和我们平常写的没有什么两样,然后我们就来实现它
packagecom.zy.zlistview.adapter;importjava.util.ArrayList;importjava.util.Arrays;importjava.util.HashSet;importjava.util.List;importjava.util.Set;importandroid.util.Log;importandroid.view.View;importandroid.view.ViewGroup;importandroid.widget.BaseAdapter;importcom.zy.zlistview.enums.Mode;importcom.zy.zlistview.listener.SwipeListener;importcom.zy.zlistview.view.ZSwipeItem;public abstract classBaseSwipeAdapterextendsBaseAdapter {public static finalString TAG ="BaseSwipeAdapter";public final intINVALID_POSITION = -1; /** * 显示模式,默认单开 */privateMode mode = Mode.Single; /** * 当前打开的item的position */protected intopenPosition = INVALID_POSITION; /** * 当前打开的所有item的position */protectedSet<Integer> openPositions =newHashSet<Integer>(); /** * 当前打开的所有ZSwipeItem对象 */protectedSet<ZSwipeItem> mShownLayouts =newHashSet<ZSwipeItem>();public abstract intgetSwipeLayoutResourceId(intposition);public abstractView generateView(intposition, ViewGroup parent);public abstract voidfillValues(intposition, View convertView); @Overridepublic finalView getView(intposition, View convertView, ViewGroup parent) {if(convertView ==null) {convertView = generateView(position, parent); initialize(convertView, position); }else{updateConvertView(convertView, position); }fillValues(position, convertView);returnconvertView; }/** * 初始化item布局调用 * *@paramtarget *@paramposition */public voidinitialize(View target,intposition) {intresId = getSwipeLayoutResourceId(position); OnLayoutListener onLayoutListener =newOnLayoutListener(position); ZSwipeItem swipeLayout = (ZSwipeItem) target.findViewById(resId);if(swipeLayout ==null)throw newIllegalStateException("can not find SwipeLayout in target view"); SwipeMemory swipeMemory =newSwipeMemory(position);// 添加滑动监听器swipeLayout.addSwipeListener(swipeMemory);// 添加布局监听器swipeLayout.addOnLayoutListener(onLayoutListener); swipeLayout.setTag(resId,newValueBox(position, swipeMemory,onLayoutListener)); mShownLayouts.add(swipeLayout); }/** * 复用item布局的时候调用 * *@paramtarget *@paramposition */public voidupdateConvertView(View target,intposition) {intresId = getSwipeLayoutResourceId(position); ZSwipeItem swipeLayout = (ZSwipeItem) target.findViewById(resId);if(swipeLayout ==null)throw newIllegalStateException("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 voidcloseAllExcept(ZSwipeItem layout) {for(ZSwipeItem s : mShownLayouts) {if(s != layout)s.close(); }}/** * 获取打开的所有的item的position信息 * *@return*/publicList<Integer> getOpenItems() {if(mode == Mode.Multiple) {return newArrayList<Integer>(openPositions); }else{returnArrays.asList(openPosition); }}/** * position位置的item是否打开 * *@paramposition *@return*/public booleanisOpen(intposition) {if(mode == Mode.Multiple) {returnopenPositions.contains(position); }else{returnopenPosition == position; }}publicMode getMode() {returnmode; }public voidsetMode(Mode mode) {this.mode = mode; openPositions.clear(); mShownLayouts.clear(); openPosition = INVALID_POSITION; }classValueBox {OnLayoutListener onLayoutListener; SwipeMemory swipeMemory;intposition; ValueBox(intposition, SwipeMemory swipeMemory, OnLayoutListener onLayoutListener) {this.swipeMemory = swipeMemory;this.onLayoutListener = onLayoutListener;this.position = position; }}public interfaceOnSwipeLayoutListener {public voidonLayout(ZSwipeItem v); }classOnLayoutListenerimplementsOnSwipeLayoutListener {private intposition; OnLayoutListener(intposition) {this.position = position; }public voidsetPosition(intposition) {this.position = position; }@Overridepublic voidonLayout(ZSwipeItem v) {if(isOpen(position)) {v.open(false,false); }else{v.close(false,false); }}}classSwipeMemoryimplementsSwipeListener {private intposition; SwipeMemory(intposition) {this.position = position; }@Overridepublic voidonClose(ZSwipeItem layout) {if(mode == Mode.Multiple) {openPositions.remove(position); }else{openPosition = INVALID_POSITION; }}@Overridepublic voidonStartOpen(ZSwipeItem layout) {if(mode == Mode.Single) {closeAllExcept(layout); }}@Overridepublic voidonOpen(ZSwipeItem layout) {if(mode == Mode.Multiple)openPositions.add(position);else{closeAllExcept(layout);openPosition = position; }}public voidsetPosition(intposition) {this.position = position; }@Overridepublic voidonStartClose(ZSwipeItem layout) {// TODO Auto-generated method stub}@Overridepublic voidonUpdate(ZSwipeItem layout,intleftOffset,inttopOffset) {//TODO Auto-generated method stub}@Overridepublic voidonHandRelease(ZSwipeItem layout,floatxvel,floatyvel) {//TODO Auto-generated method stub}}}
最后就是初始化数据以及上拉和下拉数据的加载了,直接看代码不多说:
packagecom.zy.zlistview;importjava.util.LinkedList;importandroid.app.Activity;importandroid.os.AsyncTask;importandroid.os.Bundle;importandroid.os.Handler;importandroid.view.View;importandroid.widget.AdapterView;importandroid.widget.AdapterView.OnItemClickListener;importandroid.widget.AdapterView.OnItemLongClickListener;importandroid.widget.Toast;importcom.socks.zlistview.R;importcom.zy.zlistview.view.ZListView;importcom.zy.zlistview.view.ZListView.IXListViewListener;public classMainActivityextendsActivity {protected static finalString TAG ="MainActivity";privateZListView listView;privateHandler handler =newHandler();privateLinkedList<String> lists =newLinkedList<String>();privateListViewAdapter mAdapter; @Overrideprotected voidonCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); listView = (ZListView) findViewById(R.id.listview); initData(); listView.setXListViewListener(newIXListViewListener() {@Overridepublic voidonRefresh() {handler.postDelayed(newRunnable() {@Overridepublic voidrun() {PullRefreshTask rTask =newPullRefreshTask(); rTask.execute(1000);}}, 1000); }@Overridepublic voidonLoadMore() {handler.postDelayed(newRunnable() {@Overridepublic voidrun() {UpRefreshTask rTask =newUpRefreshTask(); rTask.execute(1000);}}, 1000); }}); listView.setPullLoadEnable(true); listView.setOnItemClickListener(newOnItemClickListener() {@Overridepublic voidonItemClick(AdapterView<?> parent, View view,intposition,longid) {Toast.makeText(MainActivity.this,"onItemClick="+ position, Toast.LENGTH_SHORT).show(); }}); listView.setOnItemLongClickListener(newOnItemLongClickListener() {@Overridepublic booleanonItemLongClick(AdapterView<?> parent, View view,intposition,longid) {Toast.makeText(MainActivity.this,"onItemLongClick="+ position, Toast.LENGTH_SHORT).show();return true; }}); mAdapter =newListViewAdapter(this, lists); listView.setAdapter(mAdapter); }private voidinitData() {for(inti = 0; i < 20; i++) {lists.add("我是测试数据"+ i); }}// AsyncTask异步任务加载数据classPullRefreshTaskextendsAsyncTask<Integer, Integer, String> {@OverrideprotectedString doInBackground(Integer... params) {try{Thread.sleep(params[0]); }catch(Exception e) {e.printStackTrace(); }// 在data最前添加数据for(intj = 0; j < 5; j++) {lists.addFirst("下拉刷新加载的数据"+ j); }return null; }@Overrideprotected voidonPostExecute(String result) {super.onPostExecute(result); mAdapter.notifyDataSetChanged();//数据更新listView.stopRefresh();//头部状态重置}}// AsyncTask异步任务加载数据classUpRefreshTaskextendsAsyncTask<Integer, Integer, String> {@OverrideprotectedString doInBackground(Integer... params) {try{Thread.sleep(params[0]); }catch(Exception e) {e.printStackTrace(); }// 在data最前添加数据for(intj = 0; j < 5; j++) {lists.addLast("上拉加载的数据"+ j); }return null; }@Overrideprotected voidonPostExecute(Stringresult) {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的实现参考了大神凯子哥(/zhaokaiqiang1992/article/details)的项目,然后里面的一些bug进行了修复,但是发现还是有bug的,虽然左右滑动条目然后listView上下滚动的冲突修复了,但是还是感觉有些小问题,但是绝对是可以直接项目里使用。
源码下载:点击打开链接
醉了,要睡觉了。。。