Android自定義組合控件之自定義下拉刷新和左滑刪除實(shí)例代碼
緒論
最近項(xiàng)目里面用到了下拉刷新和左滑刪除,網(wǎng)上找了找并沒有可以用的,有比較好的左滑刪除,但是并沒有和下拉刷新上拉加載結(jié)合到一起,要不就是一些比較水的結(jié)合,并不能在項(xiàng)目里面使用,小編一著急自己組合了一個,做完了和QQ的對比了一下,并沒有太大區(qū)別,今天分享給大家,其實(shí)并不難,但是不知道為什么網(wǎng)上沒有比較好的Demo,當(dāng)你的項(xiàng)目真的很急的時候,又沒有比較好的Demo,那么“那條友誼的小船兒真是說翻就翻啊”,好了,下面先來具體看一下實(shí)現(xiàn)后的效果吧:
代碼已經(jīng)上傳到Github上了,小伙伴們記得star和follow哦
https://github.com/Hankkin/SwipeRefreshDemo
還不錯吧?比QQ多了個上拉加載,好了看看怎么實(shí)現(xiàn)的吧,小編在之前的游客評論中了解到,代碼注釋很重要,所以盡量把注釋寫的很清楚:
實(shí)現(xiàn)思路
由于時間有限,左滑菜單是在網(wǎng)上找的Demo
自定義下拉刷新頭、尾
手勢判斷,根據(jù)滑動距離顯示頭部下拉布局
判斷是否滑動到底部顯示尾部上拉布局
創(chuàng)建左滑菜單,根據(jù)手勢滑動事件彈出菜單
詳細(xì)的看一下實(shí)現(xiàn)過程
1.首先我們先自定義下拉頭布局:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="wrap_content" android:gravity="bottom" > <RelativeLayout android:id="@+id/xlistview_header_content" android:layout_width="fill_parent" android:background="#00000000" android:layout_height="60dp" > <LinearLayout android:id="@+id/xlistview_header_text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:gravity="center" android:orientation="vertical" > <TextView android:id="@+id/xlistview_header_hint_textview" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textColor="#000" android:textSize="16sp" android:text="@string/xlistview_header_hint_normal" /> <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="3dp" > <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:textColor="#000" android:text="@string/xlistview_header_last_time" android:textSize="14sp" /> <TextView android:id="@+id/xlistview_header_time" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textColor="#000" android:textSize="14sp" /> </LinearLayout> </LinearLayout> <ImageView android:id="@+id/xlistview_header_arrow" android:layout_width="wrap_content" android:layout_height="35dp" android:layout_alignLeft="@id/xlistview_header_text" android:layout_centerVertical="true" android:layout_marginLeft="-50dp" android:src="@drawable/xlistview_arrow" /> <ProgressBar android:id="@+id/xlistview_header_progressbar" android:layout_width="25dp" android:layout_height="25dp" android:layout_alignLeft="@id/xlistview_header_text" android:layout_centerVertical="true" android:layout_marginLeft="-55dp" android:visibility="gone"/> </RelativeLayout> </LinearLayout>
2.接下來我們自定義HeaderView,代碼很詳細(xì)了,不多介紹了
package com.hankkin.library; import android.content.Context; import android.util.AttributeSet; import android.view.Gravity; 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; public class RefreshListHeader extends LinearLayout { //根布局 private LinearLayout mContainer; //下拉箭頭圖片 private ImageView mArrowImageView; //下拉進(jìn)度條 private ProgressBar mProgressBar; //下拉文本 private TextView mHintTextView; //狀態(tài)值 0-正常 1-準(zhǔn)備刷新 2-正在刷新 private int mState = STATE_NORMAL; //抬起動畫 private Animation mRotateUpAnim; //下拉動畫 private Animation mRotateDownAnim; //下拉動畫時間 private final int ROTATE_ANIM_DURATION = 180; public final static int STATE_NORMAL = 0; public final static int STATE_READY = 1; public final static int STATE_REFRESHING = 2; public RefreshListHeader(Context context) { super(context); initView(context); } /** * @param context * @param attrs */ public RefreshListHeader(Context context, AttributeSet attrs) { super(context, attrs); initView(context); } /** * 初始化組件 * @param context */ private void initView(Context context) { LayoutParams lp = new LayoutParams(LayoutParams.FILL_PARENT, 0); mContainer = (LinearLayout) LayoutInflater.from(context).inflate(R.layout.xlistview_header, null); addView(mContainer, lp); setGravity(Gravity.BOTTOM); mArrowImageView = (ImageView) findViewById(R.id.xlistview_header_arrow); mHintTextView = (TextView) findViewById(R.id.xlistview_header_hint_textview); mProgressBar = (ProgressBar) findViewById(R.id.xlistview_header_progressbar); //設(shè)置抬起動畫 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); //設(shè)置下拉動畫 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); } //設(shè)置狀態(tài) public void setState(int state) { if (state == mState) return; //正在刷新時-顯示進(jìn)度條,隱藏下拉箭頭 if (state == STATE_REFRESHING) { mArrowImageView.clearAnimation(); mArrowImageView.setVisibility(View.INVISIBLE); mProgressBar.setVisibility(View.VISIBLE); } else { //顯示下拉箭頭,隱藏進(jìn)度條 mArrowImageView.setVisibility(View.VISIBLE); mProgressBar.setVisibility(View.INVISIBLE); } switch (state) { case STATE_NORMAL: if (mState == STATE_READY) {//準(zhǔn)備刷新時開啟動畫 mArrowImageView.startAnimation(mRotateDownAnim); } if (mState == STATE_REFRESHING) {//正在刷新時開啟動畫 mArrowImageView.clearAnimation(); } mHintTextView.setText(R.string.xlistview_header_hint_normal); break; case STATE_READY: if (mState != STATE_READY) { mArrowImageView.clearAnimation(); mArrowImageView.startAnimation(mRotateUpAnim); mHintTextView.setText(R.string.xlistview_header_hint_ready); } break; case STATE_REFRESHING: mHintTextView.setText(R.string.xlistview_header_hint_loading); break; default: } mState = state; } /** * 設(shè)置頭部高度 * @param height */ public void setVisiableHeight(int height) { if (height < 0) height = 0; LayoutParams lp = (LayoutParams) mContainer.getLayoutParams(); lp.height = height; mContainer.setLayoutParams(lp); } public int getVisiableHeight() { return mContainer.getHeight(); } 3.HeaderView定義結(jié)束后,我們需要自定義一個既支持下拉刷新又支持左滑刪除的ListView,看看我是怎么做的:(左滑菜單是引用網(wǎng)上的Demo,代碼寫的也比較易懂,這里不詳細(xì)給大家介紹了) 然后我們在他的基礎(chǔ)上添加下拉上拉事件:(重點(diǎn)看一下onTouchEvent事件) @Override public boolean onTouchEvent(MotionEvent ev) { if (mLastY == -1) { //獲取上次y軸坐標(biāo) mLastY = ev.getRawY(); } switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: //手勢按下事件、獲取坐標(biāo)、設(shè)置上次下拉時間 firstTouchY = ev.getRawY(); mLastY = ev.getRawY(); setRefreshTime(RefreshTime.getRefreshTime(getContext())); int oldPos = mTouchPosition; mDownX = ev.getX(); mDownY = ev.getY(); mTouchState = TOUCH_STATE_NONE; mTouchPosition = pointToPosition((int) ev.getX(), (int) ev.getY()); //彈出左滑菜單 if (mTouchPosition == oldPos && mTouchView != null && mTouchView.isOpen()) { mTouchState = TOUCH_STATE_X; mTouchView.onSwipe(ev);//左滑菜單手勢監(jiān)聽事件,根據(jù)滑動距離彈出菜單 return true; } //獲取item view,此方法是因?yàn)間etChildAt()傳入index值導(dǎo)致listview不可見的item會報空指針 // 防止listview不可見的item獲取到的為空,使用下面方法 View view = getChildAt(mTouchPosition - getFirstVisiblePosition()); if (mTouchView != null && mTouchView.isOpen()) {//如果滑動的item不為空并且已經(jīng)開啟,則關(guān)閉該菜單 mTouchView.smoothCloseMenu(); mTouchView = null; return super.onTouchEvent(ev); } if (mTouchView != null) {//否則打開左滑菜單 mTouchView.onSwipe(ev); } if (view instanceof SwipeMenuLayout) { mTouchView = (SwipeMenuLayout) view; } break; case MotionEvent.ACTION_MOVE://手勢滑動事件 final float deltaY = ev.getRawY() - mLastY; float dy = Math.abs((ev.getY() - mDownY)); float dx = Math.abs((ev.getX() - mDownX)); mLastY = ev.getRawY(); //判斷左滑菜單是否未激活、或者x軸偏移平方小于y軸偏移平方3倍的時候 if ((mTouchView == null || !mTouchView.isActive()) && Math.pow(dx, 2) / Math.pow(dy, 2) <= 3) { //判斷第一個可見位置并且頭部布局可見高度大于0時或者y軸偏移量>0 if (getFirstVisiblePosition() == 0 && (mHeaderView.getVisiableHeight() > 0 || deltaY > 0)) { // 重新更新頭部高度 updateHeaderHeight(deltaY / OFFSET_RADIO); invokeOnScrolling(); } } if (mTouchState == TOUCH_STATE_X) {//如果x軸偏移彈出左滑菜單 if (mTouchView != null) { mTouchView.onSwipe(ev); } getSelector().setState(new int[] { 0 }); ev.setAction(MotionEvent.ACTION_CANCEL); super.onTouchEvent(ev); return true; } else if (mTouchState == TOUCH_STATE_NONE) { if (Math.abs(dy) > MAX_Y) { //如果y軸偏移量>指定y軸偏移量,設(shè)置y軸偏移狀態(tài) mTouchState = TOUCH_STATE_Y; } else if (dx > MAX_X) {//如果x軸偏移量>指定x軸偏移量,設(shè)置x軸偏移狀態(tài),開始彈出左滑菜單 mTouchState = TOUCH_STATE_X; if (mOnSwipeListener != null) { mOnSwipeListener.onSwipeStart(mTouchPosition); } } } break; case MotionEvent.ACTION_UP://手勢抬起事件 mLastY = -1; // reset if (getFirstVisiblePosition() == 0) { // 設(shè)置下拉刷新狀態(tài)值,開啟下拉刷新狀態(tài) if (mEnablePullRefresh && mHeaderView.getVisiableHeight() > mHeaderViewHeight) { mPullRefreshing = true; mHeaderView.setState(RefreshListHeader.STATE_REFRESHING); if (onRefreshListener != null) { tag=REFRESH; onRefreshListener.onRefresh(); } } resetHeaderHeight(); } lastTouchY = ev.getRawY();//獲取上次y軸偏移量 if (canLoadMore()) {//判斷是否滿足上拉 loadData(); } if (mTouchState == TOUCH_STATE_X) {//如果為x軸偏移狀態(tài),開啟左滑 if (mTouchView != null) { mTouchView.onSwipe(ev); if (!mTouchView.isOpen()) { mTouchPosition = -1; mTouchView = null; } } if (mOnSwipeListener != null) { mOnSwipeListener.onSwipeEnd(mTouchPosition); } ev.setAction(MotionEvent.ACTION_CANCEL); super.onTouchEvent(ev); return true; } break; } return super.onTouchEvent(ev); }
當(dāng)然這部分也是滑動沖突解決的主要部分,看一下完整的代碼,應(yīng)該能夠看的懂了,小編幾乎每一句都加上了注釋,如果有看不明白的地方可以留言,小編會及時回復(fù)的:
package com.hankkin.library; import android.content.Context; import android.os.AsyncTask; import android.util.AttributeSet; import android.util.TypedValue; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.ViewTreeObserver.OnGlobalLayoutListener; import android.view.animation.DecelerateInterpolator; import android.view.animation.Interpolator; import android.widget.AbsListView; import android.widget.AbsListView.OnScrollListener; import android.widget.LinearLayout; import android.widget.ListAdapter; import android.widget.ListView; import android.widget.RelativeLayout; import android.widget.Scroller; import android.widget.TextView; import java.util.Date; public class RefreshSwipeMenuListView extends ListView implements OnScrollListener { private static final int TOUCH_STATE_NONE = 0; private static final int TOUCH_STATE_X = 1; //x軸觸摸狀態(tài)值 private static final int TOUCH_STATE_Y = 2; //y軸觸摸狀態(tài)值 public static final int BOTH=2;//上拉和下拉 public static final int HEADER=0;//下拉 public static final int FOOTER=1;//上拉 public static String tag;//ListView的動作 public static final String REFRESH="refresh"; public static final String LOAD="load"; private int MAX_Y = 5; //Y軸最大偏移量 private int MAX_X = 3; //X軸最大偏移量 private float mDownX; //觸摸x private float mDownY; //觸摸y private int mTouchState; //觸摸狀態(tài) private int mTouchPosition; //觸摸位置 private SwipeMenuLayout mTouchView; //滑動彈出布局 private OnSwipeListener mOnSwipeListener; //彈出監(jiān)聽器 private float firstTouchY; //第一次觸摸y坐標(biāo) private float lastTouchY; //最后一次觸摸y坐標(biāo) //創(chuàng)建左滑菜單接口 private SwipeMenuCreator mMenuCreator; //菜單點(diǎn)擊事件 private OnMenuItemClickListener mOnMenuItemClickListener; //關(guān)閉菜單動畫修飾Interpolator private Interpolator mCloseInterpolator; //開啟菜單動畫修飾Interpolator private Interpolator mOpenInterpolator; private float mLastY = -1; private Scroller mScroller; private OnScrollListener mScrollListener; // 滑動監(jiān)聽 // 下拉上拉監(jiān)聽器 private OnRefreshListener onRefreshListener; //下拉頭 private RefreshListHeader mHeaderView; //頭部視圖內(nèi)容,用來計算頭部高度,不下拉時隱藏 private RelativeLayout mHeaderViewContent; //下拉時間文本控件 private TextView mHeaderTimeView; private int mHeaderViewHeight; // 頭部高度 private boolean mEnablePullRefresh = true;//能否下拉刷新 private boolean mPullRefreshing = false; // 是否正在刷新 //上拉尾部視圖 private LinearLayout mFooterView; private boolean mEnablePullLoad;//是否可以上拉加載 private boolean mPullLoading; //是否正在上拉 private boolean mIsFooterReady = false; private int mTotalItemCount; private int mScrollBack; 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 = 50; private final static float OFFSET_RADIO = 1.8f; private boolean isFooterVisible=false; public RefreshSwipeMenuListView(Context context) { super(context); init(context); } public RefreshSwipeMenuListView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); init(context); } public RefreshSwipeMenuListView(Context context, AttributeSet attrs) { super(context, attrs); init(context); } /** * 初始化組件 * @param context */ private void init(Context context) { mScroller = new Scroller(context, new DecelerateInterpolator()); super.setOnScrollListener(this); // 初始化頭部視圖 mHeaderView = new RefreshListHeader(context); mHeaderViewContent = (RelativeLayout) mHeaderView.findViewById(R.id.xlistview_header_content); mHeaderTimeView = (TextView) mHeaderView.findViewById(R.id.xlistview_header_time); addHeaderView(mHeaderView); // 初始化尾部視圖 mFooterView = (LinearLayout) LayoutInflater.from(context).inflate(R.layout.xlistview_footer, null, false); // 初始化頭部高度 mHeaderView.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() { @Override public void onGlobalLayout() { mHeaderViewHeight = mHeaderViewContent.getHeight(); //向 ViewTreeObserver 注冊方法,以獲取控件尺寸 getViewTreeObserver().removeGlobalOnLayoutListener(this); } }); MAX_X = dp2px(MAX_X); MAX_Y = dp2px(MAX_Y); mTouchState = TOUCH_STATE_NONE; } /** * 添加適配器 * @param adapter */ @Override public void setAdapter(ListAdapter adapter) { if (mIsFooterReady == false) { //添加尾部隱藏 mIsFooterReady = true; addFooterView(mFooterView); mFooterView.setVisibility(GONE); } super.setAdapter(new SwipeMenuAdapter(getContext(), adapter) { @Override public void createMenu(SwipeMenu menu) {//創(chuàng)建左滑菜單 if (mMenuCreator != null) { mMenuCreator.create(menu); } } @Override public void onItemClick(SwipeMenuView view, SwipeMenu menu, int index) { if (mOnMenuItemClickListener != null) {//左滑菜單點(diǎn)擊事件 mOnMenuItemClickListener.onMenuItemClick(view.getPosition(), menu, index); } if (mTouchView != null) { mTouchView.smoothCloseMenu(); } } }); } public void setCloseInterpolator(Interpolator interpolator) { mCloseInterpolator = interpolator; } public void setOpenInterpolator(Interpolator interpolator) { mOpenInterpolator = interpolator; } public Interpolator getOpenInterpolator() { return mOpenInterpolator; } public Interpolator getCloseInterpolator() { return mCloseInterpolator; } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { return super.onInterceptTouchEvent(ev); } @Override public boolean onTouchEvent(MotionEvent ev) { if (mLastY == -1) { //獲取上次y軸坐標(biāo) mLastY = ev.getRawY(); } switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: //手勢按下事件、獲取坐標(biāo)、設(shè)置上次下拉時間 firstTouchY = ev.getRawY(); mLastY = ev.getRawY(); setRefreshTime(RefreshTime.getRefreshTime(getContext())); int oldPos = mTouchPosition; mDownX = ev.getX(); mDownY = ev.getY(); mTouchState = TOUCH_STATE_NONE; mTouchPosition = pointToPosition((int) ev.getX(), (int) ev.getY()); //彈出左滑菜單 if (mTouchPosition == oldPos && mTouchView != null && mTouchView.isOpen()) { mTouchState = TOUCH_STATE_X; mTouchView.onSwipe(ev);//左滑菜單手勢監(jiān)聽事件,根據(jù)滑動距離彈出菜單 return true; } //獲取item view,此方法是因?yàn)間etChildAt()傳入index值導(dǎo)致listview不可見的item會報空指針 // 防止listview不可見的item獲取到的為空,使用下面方法 View view = getChildAt(mTouchPosition - getFirstVisiblePosition()); if (mTouchView != null && mTouchView.isOpen()) {//如果滑動的item不為空并且已經(jīng)開啟,則關(guān)閉該菜單 mTouchView.smoothCloseMenu(); mTouchView = null; return super.onTouchEvent(ev); } if (mTouchView != null) {//否則打開左滑菜單 mTouchView.onSwipe(ev); } if (view instanceof SwipeMenuLayout) { mTouchView = (SwipeMenuLayout) view; } break; case MotionEvent.ACTION_MOVE://手勢滑動事件 final float deltaY = ev.getRawY() - mLastY; float dy = Math.abs((ev.getY() - mDownY)); float dx = Math.abs((ev.getX() - mDownX)); mLastY = ev.getRawY(); //判斷左滑菜單是否未激活、或者x軸偏移平方小于y軸偏移平方3倍的時候 if ((mTouchView == null || !mTouchView.isActive()) && Math.pow(dx, 2) / Math.pow(dy, 2) <= 3) { //判斷第一個可見位置并且頭部布局可見高度大于0時或者y軸偏移量>0 if (getFirstVisiblePosition() == 0 && (mHeaderView.getVisiableHeight() > 0 || deltaY > 0)) { // 重新更新頭部高度 updateHeaderHeight(deltaY / OFFSET_RADIO); invokeOnScrolling(); } } if (mTouchState == TOUCH_STATE_X) {//如果x軸偏移彈出左滑菜單 if (mTouchView != null) { mTouchView.onSwipe(ev); } getSelector().setState(new int[] { 0 }); ev.setAction(MotionEvent.ACTION_CANCEL); super.onTouchEvent(ev); return true; } else if (mTouchState == TOUCH_STATE_NONE) { if (Math.abs(dy) > MAX_Y) { //如果y軸偏移量>指定y軸偏移量,設(shè)置y軸偏移狀態(tài) mTouchState = TOUCH_STATE_Y; } else if (dx > MAX_X) {//如果x軸偏移量>指定x軸偏移量,設(shè)置x軸偏移狀態(tài),開始彈出左滑菜單 mTouchState = TOUCH_STATE_X; if (mOnSwipeListener != null) { mOnSwipeListener.onSwipeStart(mTouchPosition); } } } break; case MotionEvent.ACTION_UP://手勢抬起事件 mLastY = -1; // reset if (getFirstVisiblePosition() == 0) { // 設(shè)置下拉刷新狀態(tài)值,開啟下拉刷新狀態(tài) if (mEnablePullRefresh && mHeaderView.getVisiableHeight() > mHeaderViewHeight) { mPullRefreshing = true; mHeaderView.setState(RefreshListHeader.STATE_REFRESHING); if (onRefreshListener != null) { tag=REFRESH; onRefreshListener.onRefresh(); } } resetHeaderHeight(); } lastTouchY = ev.getRawY();//獲取上次y軸偏移量 if (canLoadMore()) {//判斷是否滿足上拉 loadData(); } if (mTouchState == TOUCH_STATE_X) {//如果為x軸偏移狀態(tài),開啟左滑 if (mTouchView != null) { mTouchView.onSwipe(ev); if (!mTouchView.isOpen()) { mTouchPosition = -1; mTouchView = null; } } if (mOnSwipeListener != null) { mOnSwipeListener.onSwipeEnd(mTouchPosition); } ev.setAction(MotionEvent.ACTION_CANCEL); super.onTouchEvent(ev); return true; } break; } return super.onTouchEvent(ev); } class ResetHeaderHeightTask extends AsyncTask<Void, Void, Void> { protected Void doInBackground(Void... params) { try { Thread.sleep(400); } catch (InterruptedException e) { e.printStackTrace(); } return null; } protected void onPostExecute(Void result) { mPullRefreshing = false; mHeaderView.setState(RefreshListHeader.STATE_NORMAL); resetHeaderHeight(); } } public void smoothOpenMenu(int position) { if (position >= getFirstVisiblePosition() && position <= getLastVisiblePosition()) { View view = getChildAt(position - getFirstVisiblePosition()); if (view instanceof SwipeMenuLayout) { mTouchPosition = position; if (mTouchView != null && mTouchView.isOpen()) { mTouchView.smoothCloseMenu(); } mTouchView = (SwipeMenuLayout) view; mTouchView.smoothOpenMenu(); } } } private int dp2px(int dp) { return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, getContext().getResources().getDisplayMetrics()); } public void setMenuCreator(SwipeMenuCreator menuCreator) { this.mMenuCreator = menuCreator; } public void setOnMenuItemClickListener(OnMenuItemClickListener onMenuItemClickListener) { this.mOnMenuItemClickListener = onMenuItemClickListener; } public void setOnSwipeListener(OnSwipeListener onSwipeListener) { this.mOnSwipeListener = onSwipeListener; } public static interface OnMenuItemClickListener { void onMenuItemClick(int position, SwipeMenu menu, int index); } public static interface OnSwipeListener { void onSwipeStart(int position); void onSwipeEnd(int position); } /** * 設(shè)置刷新可用 * @param enable */ private void setPullRefreshEnable(boolean enable) { mEnablePullRefresh = enable; if (!mEnablePullRefresh) { // disable, hide the content mHeaderViewContent.setVisibility(View.INVISIBLE); } else { mHeaderViewContent.setVisibility(View.VISIBLE); } } /** * enable or disable pull up load more feature. * 設(shè)置加載可用 * @param enable */ private void setPullLoadEnable(boolean enable) { mEnablePullLoad = enable; if (!mEnablePullLoad) { mFooterView.setVisibility(GONE); mFooterView.setOnClickListener(null); } else { mPullLoading = false; mFooterView.setVisibility(VISIBLE); // both "pull up" and "click" will invoke load more. mFooterView.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { startLoadMore(); } }); } } /** * stop refresh, reset header view. * 停止刷新,重置頭部控件 */ private void stopRefresh() { if (mPullRefreshing == true) { mPullRefreshing = false; resetHeaderHeight(); } } /** * stop load more, reset footer view. * 停止加載更多,重置尾部控件 */ private void stopLoadMore() { if (mPullLoading == true) { mPullLoading = false; mFooterView.setVisibility(GONE); } } /** * set last refresh time * * @param time */ public void setRefreshTime(String time) { mHeaderTimeView.setText(time); } private void invokeOnScrolling() { if (mScrollListener instanceof OnXScrollListener) { OnXScrollListener l = (OnXScrollListener) mScrollListener; l.onXScrolling(this); } } /** * 更新頭部高度,設(shè)置狀態(tài)值 * @param delta */ private void updateHeaderHeight(float delta) { mHeaderView.setVisiableHeight((int) delta + mHeaderView.getVisiableHeight()); if (mEnablePullRefresh && !mPullRefreshing) { if (mHeaderView.getVisiableHeight() > mHeaderViewHeight) { mHeaderView.setState(RefreshListHeader.STATE_READY); } else { mHeaderView.setState(RefreshListHeader.STATE_NORMAL); } } setSelection(0); // scroll to top each time } /** * 重置頭部視圖高度 */ private void resetHeaderHeight() { int height = mHeaderView.getVisiableHeight(); if (height == 0) // 不可見 return; // 如果正在刷新并且頭部高度沒有完全顯示不做操作 if (mPullRefreshing && height <= mHeaderViewHeight) { return; } int finalHeight = 0; // 默認(rèn) //如果正在刷新并且滑動高度大于頭部高度 if (mPullRefreshing && height > mHeaderViewHeight) { finalHeight = mHeaderViewHeight; } mScrollBack = SCROLLBACK_HEADER; mScroller.startScroll(0, height, 0, finalHeight - height, SCROLL_DURATION); // 觸發(fā)computescroll invalidate(); } /** * 開啟上啦 */ private void startLoadMore() { mPullLoading = true; mFooterView.setVisibility(VISIBLE); if (onRefreshListener != null) { tag=LOAD; onRefreshListener.onLoadMore(); } } @Override public void computeScroll() { if (mScroller.computeScrollOffset()) { if (mScrollBack == SCROLLBACK_HEADER) { mHeaderView.setVisiableHeight(mScroller.getCurrY()); } postInvalidate(); invokeOnScrolling(); } super.computeScroll(); } @Override public void setOnScrollListener(OnScrollListener l) { mScrollListener = l; } @Override public void onScrollStateChanged(AbsListView view, int scrollState) { if (mScrollListener != null) { mScrollListener.onScrollStateChanged(view, scrollState); } } /** * 滑動監(jiān)聽 * @param view * @param firstVisibleItem * @param visibleItemCount * @param totalItemCount */ @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { mTotalItemCount = totalItemCount; if (mScrollListener != null) { mScrollListener.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount); } if(firstVisibleItem+visibleItemCount>=totalItemCount){ isFooterVisible=true; }else{ isFooterVisible=false; } } public void setOnRefreshListener(OnRefreshListener l) { onRefreshListener = l; } /** * you can listen ListView.OnScrollListener or this one. it will invoke * onXScrolling when header/footer scroll back. */ public interface OnXScrollListener extends OnScrollListener { public void onXScrolling(View view); } /** * implements this interface to get refresh/load more event. */ public interface OnRefreshListener { public void onRefresh(); public void onLoadMore(); } /** * 上拉加載和下拉刷新請求完畢 */ public void complete(){ stopLoadMore(); stopRefresh(); if(REFRESH.equals(tag)){ RefreshTime.setRefreshTime(getContext(),new Date()); } } /** * 設(shè)置ListView的模式,上拉和下拉 * @param mode */ public void setListViewMode(int mode){ if(mode==BOTH){ setPullRefreshEnable(true); setPullLoadEnable(true); }else if(mode==FOOTER){ setPullLoadEnable(true); }else if(mode==HEADER){ setPullRefreshEnable(true); } } /** * 判斷是否可以上蠟加載 * @return */ private boolean canLoadMore() { return isBottom() && !mPullLoading && isPullingUp(); } /** * 判斷是否到達(dá)底部 * @return */ private boolean isBottom() { if (getCount() > 0) { if (getLastVisiblePosition() == getAdapter().getCount() - 1 && getChildAt(getChildCount() - 1).getBottom() <= getHeight()) { return true; } } return false; } private boolean isPullingUp() { return (firstTouchY - lastTouchY) >= 200; } private void loadData() { if (onRefreshListener != null) { setLoading(true); } } /** * 設(shè)置是否上拉 * @param loading */ public void setLoading(boolean loading) { if (this == null) return; mPullLoading = loading; if (loading) { mFooterView.setVisibility(VISIBLE); setSelection(getAdapter().getCount() - 1); onRefreshListener.onLoadMore(); } else { mFooterView.setVisibility(GONE); firstTouchY = 0; lastTouchY = 0; } }
定義完了之后我們是這樣用的:
rsmLv.setAdapter(adapter); rsmLv.setListViewMode(RefreshSwipeMenuListView.HEADER); rsmLv.setOnRefreshListener(this); SwipeMenuCreator creator = new SwipeMenuCreator() { @Override public void create(SwipeMenu menu) { // 創(chuàng)建滑動選項(xiàng) SwipeMenuItem rejectItem = new SwipeMenuItem( getApplicationContext()); // 設(shè)置選項(xiàng)背景 rejectItem.setBackground(new ColorDrawable(getResources().getColor(R.color.top))); // 設(shè)置選項(xiàng)寬度 rejectItem.setWidth(dp2px(80,getApplicationContext())); // 設(shè)置選項(xiàng)標(biāo)題 rejectItem.setTitle("置頂"); // 設(shè)置選項(xiàng)標(biāo)題 rejectItem.setTitleSize(16); // 設(shè)置選項(xiàng)標(biāo)題顏色 rejectItem.setTitleColor(Color.WHITE); // 添加選項(xiàng) menu.addMenuItem(rejectItem); // 創(chuàng)建刪除選項(xiàng) SwipeMenuItem argeeItem = new SwipeMenuItem(getApplicationContext()); argeeItem.setBackground(new ColorDrawable(getResources().getColor(R.color.del))); argeeItem.setWidth(dp2px(80, getApplicationContext())); argeeItem.setTitle("刪除"); argeeItem.setTitleSize(16); argeeItem.setTitleColor(Color.WHITE); menu.addMenuItem(argeeItem); } }; rsmLv.setMenuCreator(creator); rsmLv.setOnMenuItemClickListener(new RefreshSwipeMenuListView.OnMenuItemClickListener() { @Override public void onMenuItemClick(int position, SwipeMenu menu, int index) { switch (index) { case 0: //第一個選項(xiàng) Toast.makeText(MainActivity.this,"您點(diǎn)擊的是置頂",Toast.LENGTH_SHORT).show(); break; case 1: //第二個選項(xiàng) del(position,rsmLv.getChildAt(position+1-rsmLv.getFirstVisiblePosition())); break; } } });
為了和QQ有著一樣的體驗(yàn)效果,小編加了一個刪除item的動畫
看起來就不會太生硬了,用戶體驗(yàn)也比較好:
/** * 刪除item動畫 * @param index * @param v */ private void del(final int index, View v){ final Animation animation = (Animation) AnimationUtils.loadAnimation(v.getContext(), R.anim.list_anim); animation.setAnimationListener(new Animation.AnimationListener() { public void onAnimationStart(Animation animation) {} public void onAnimationRepeat(Animation animation) {} public void onAnimationEnd(Animation animation) { data.remove(index); adapter.notifyDataSetChanged(); animation.cancel(); } }); v.startAnimation(animation); }
好了到這里了,我們的組合控件-上拉下拉+左滑刪除的組合ListView就定義好了,分享出來小伙伴們以后遇到可以直接拿來用,”到時候小船兒也不會再翻了”,不合理的地方希望大家提出來,共同交流,進(jìn)步。
以上所述是小編給大家介紹的Android自定義組合控件之自定義下拉刷新和左滑刪除實(shí)例代碼,希望對大家有所幫助!
相關(guān)文章
Android使用Notification在狀態(tài)欄上顯示通知
這篇文章主要為大家詳細(xì)介紹了Android使用Notification在狀態(tài)欄上顯示通知,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-12-12解決Android Studio 出現(xiàn)“Cannot resolve symbo
今天在調(diào)試的時候,Android Studio報了一個莫名其妙的錯誤Cannot resolve symbol'R'讓人不知所措,因?yàn)檫@東西根本不歸我管啊,怎么會出現(xiàn) Cannot resolve symbol 這種錯誤呢?下面給大家分享Android Studio 出現(xiàn)“Cannot resolve symbol”解決方案,需要的朋友可以參考下2023-03-03解決Android-RecyclerView列表倒計時錯亂問題
這篇文章主要介紹了解決Android-RecyclerView列表倒計時錯亂問題,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-08-08Android內(nèi)存泄漏排查利器LeakCanary
這篇文章主要為大家詳細(xì)介紹了Android內(nèi)存泄漏排查利器LeakCanary的相關(guān)資料,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-03-03Android 自定義控件實(shí)現(xiàn)顯示文字的功能
這篇文章主要介紹了Android 自定義控件實(shí)現(xiàn)顯示文字的功能的相關(guān)資料,需要的朋友可以參考下2016-11-11Android開發(fā)之FloatingActionButton懸浮按鈕基本使用、字體、顏色用法示例
這篇文章主要介紹了Android開發(fā)之FloatingActionButton懸浮按鈕基本使用、字體、顏色用法,結(jié)合實(shí)例形式分析了Android FloatingActionButton懸浮按鈕的基本功能、布局、使用方法及操作注意事項(xiàng),需要的朋友可以參考下2019-03-03