class="FocusMe"> 大家对这些功能都是看的多了,然后对上拉刷新和下拉加载的原理都是非常清楚的,所以实现这功能其实也就是为了让大家能够从众多的同行们来进行比较学习而已,虽然即使是这样,但是面试的时候面试官还是会问你上拉和下拉是怎么实现的,滑动删除功能是怎么实现,其实要实现这些功能又不是唯一的方法,但是基本上思想都是一致的。然后gitup上的这些例子是非常的多,然后实现的也是大同小异但是也不能不让我们去球童存异。作为天朝的程序员即使是一个伸手党也不必太觉得羞耻,能把别人的东西来改一改或者沿用别人的思想来模仿也是不错的。然后公司的项目也可能不建议直接去导入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;