引子
移动平台上很常见的侧滑菜单布局,本文将给出控件源码,以及调用的代码。
效果动态图
源代码
自定义ViewGroup : Swipe2DeleteViewGroup.java
1 package com.example.administrator.technologystackapp.activities.custom; 2 3 import android.content.Context; 4 import android.graphics.Rect; 5 import android.util.AttributeSet; 6 import android.view.MotionEvent; 7 import android.view.VelocityTracker; 8 import android.view.View; 9 import android.view.ViewConfiguration; 10 import android.view.ViewGroup; 11 import android.widget.Scroller; 12 13 14 /** 15 * Created by wupengjian on 16/11/9. 16 * <p/> 18 */ 19 public class Swipe2DeleteViewGroup extends ViewGroup { 20 21 private static final int STATUS_NORMAL = 0; 22 private static final int STATUS_EXPAND = 1; 23 private static final int HOVER_TAP_SLOP = 10; 24 private static final int HOVER_TAP_TIMEOUT = 150; 25 private View mCenterView; 26 private Scroller mScroller; 27 private VelocityTracker mVelocityTracker = null; 28 private ViewConfiguration mViewConfiguration; 29 private float mLastTouchX, mScrollX; 30 private int mMaxScrollDistance, mMinScrollDistance; 31 private int mStatus = STATUS_NORMAL; 32 private MotionEvent mMoveDownEvent; 33 private OnItemClickListener mOnItemClickListener; 34 35 public Swipe2DeleteViewGroup(Context context) { 36 this(context, null); 37 } 38 39 public Swipe2DeleteViewGroup(Context context, AttributeSet attrs) { 40 this(context, attrs, 0); 41 } 42 43 public Swipe2DeleteViewGroup(Context context, AttributeSet attrs, int defStyleAttr) { 44 super(context, attrs, defStyleAttr); 45 mScroller = new Scroller(context); 46 mViewConfiguration = ViewConfiguration.get(context); 47 } 48 49 @Override 50 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 51 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 52 53 int widthMode = MeasureSpec.getMode(widthMeasureSpec); 54 int widthSize = MeasureSpec.getSize(widthMeasureSpec); 55 int heightMode = MeasureSpec.getMode(heightMeasureSpec); 56 int heightSize = MeasureSpec.getSize(heightMeasureSpec); 57 58 mCenterView = null; 59 mMaxScrollDistance = 0; 60 61 int childCount = getChildCount(); 62 for (int i = 0; i < childCount; i++) { 63 View child = getChildAt(i); 64 if (child.getVisibility() == GONE) { 65 continue; 66 } 67 int childWidth; 68 if (mCenterView == null) { 69 mCenterView = child; 70 childWidth = MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.EXACTLY); 71 } else { 72 childWidth = heightMeasureSpec; 73 //最大滚动距离就是所有菜单item的宽度的和 74 mMaxScrollDistance += MeasureSpec.getSize(childWidth); 75 } 76 child.measure(childWidth, heightMeasureSpec); 77 } 78 setMeasuredDimension(widthSize, heightSize); 79 } 80 81 @Override 82 protected void onLayout(boolean changed, int l, int t, int r, int b) { 83 84 int childCount = getChildCount(); 85 int offset = 0; 86 for (int i = 0; i < childCount; i++) { 87 View child = getChildAt(i); 88 if (child.getVisibility() == GONE) { 89 continue; 90 } 91 setChildFrame(child, offset, 0, child.getMeasuredWidth(), child.getMeasuredHeight()); 92 offset += child.getMeasuredWidth(); 93 } 94 } 95 96 private void setChildFrame(View child, int left, int top, int width, int height) { 97 child.layout(left, top, left + width, top + height); 98 } 99 100 @Override 101 public boolean onTouchEvent(MotionEvent event) { 102 super.onTouchEvent(event); 103 switch (event.getAction()) { 104 case MotionEvent.ACTION_DOWN: 105 mMoveDownEvent = MotionEvent.obtain(event); 106 if (mVelocityTracker == null) { 107 mVelocityTracker = VelocityTracker.obtain(); 108 } else { 109 mVelocityTracker.clear(); 110 } 111 mVelocityTracker.addMovement(event); 112 break; 113 case MotionEvent.ACTION_MOVE: 114 115 mVelocityTracker.addMovement(event); 116 updateScrollX(mLastTouchX - event.getRawX()); 117 break; 118 case MotionEvent.ACTION_UP: 119 int downX = (int) mMoveDownEvent.getRawX(); 120 int downY = (int) mMoveDownEvent.getRawY(); 121 //如果事件坐标在以按下时坐标为中心的宽度为 2 * HOVER_TAP_SLOP 的正方形内,则认为这个事件是点击事件 122 Rect rect = new Rect(downX - HOVER_TAP_SLOP, downY - HOVER_TAP_SLOP, downX + HOVER_TAP_SLOP, downY + HOVER_TAP_SLOP); 123 //如果按下手指和抬起手指时的 坐标和时间 相差不是很大,则可以认为是点击 124 boolean intent2Click = rect.contains((int) event.getRawX(), (int) event.getRawY()); 125 boolean isTimeNotTooLong = event.getEventTime() - event.getDownTime() < HOVER_TAP_TIMEOUT; 126 if (intent2Click && isTimeNotTooLong) { 127 128 handleClickEvent(event); 129 } else { 130 131 mVelocityTracker.computeCurrentVelocity(1000); 132 float velocityX = mVelocityTracker.getXVelocity(); 133 if (Math.abs(velocityX) > mViewConfiguration.getScaledMinimumFlingVelocity()) { 134 if (velocityX > 0) { 135 hideMenu(); 136 } else { 137 showMenu(); 138 } 139 } else { 140 141 toggleStatus(); 142 } 143 } 144 if (mVelocityTracker != null) { 145 mVelocityTracker.recycle(); 146 mVelocityTracker = null; 147 } 148 break; 149 case MotionEvent.ACTION_CANCEL: 150 toggleStatus(); 151 break; 152 } 153 mLastTouchX = event.getRawX(); 154 return true; 155 } 156 157 /** 158 * 处理点击事件 159 * 160 * @param event 161 */ 162 private void handleClickEvent(MotionEvent event) { 163 164 int index = 0; 165 int childCount = getChildCount(); 166 for (int i = 0; i < childCount; i++) { 167 View child = getChildAt(i); 168 if (child.getVisibility() == GONE) { 169 continue; 170 } 171 if (isMotionEventInView(child, event)) { 172 //如果点击的是主item,如果当前是菜单展开状态,则先收起菜单,并消费此次点击 173 if (child == mCenterView && mStatus != STATUS_NORMAL) { 174 175 hideMenu(); 176 } else if (null != mOnItemClickListener) { 177 178 mOnItemClickListener.onItemClick(child, index, child == mCenterView); 179 } 180 break; 181 } 182 index++; 183 } 184 } 185 186 /** 187 * 判断点击事件是否在view中 188 * 189 * @param view 190 * @param event 191 * @return 192 */ 193 private boolean isMotionEventInView(View view, MotionEvent event) { 194 int[] location = new int[2]; 195 view.getLocationOnScreen(location); 196 int x = location[0]; 197 int y = location[1]; 198 boolean isBeyondLeft = event.getRawX() < x; 199 boolean isBeyondTop = event.getRawY() < y; 200 boolean isBeyondRight = event.getRawX() > (x + view.getMeasuredWidth()); 201 boolean isBeyondBottom = event.getRawY() > (y + view.getMeasuredHeight()); 202 return !isBeyondLeft && !isBeyondTop && !isBeyondRight && !isBeyondBottom; 203 } 204 205 private void toggleStatus() { 206 int scrollThreshold = getMeasuredHeight(); 207 if (mScrollX < scrollThreshold) { 208 209 hideMenu(); 210 } else if (mScrollX > scrollThreshold) { 211 212 showMenu(); 213 } 214 } 215 216 /** 217 * 显示菜单 218 */ 219 private void showMenu() { 220 221 mStatus = STATUS_EXPAND; 222 updateScrollX(mMaxScrollDistance); 223 } 224 225 /** 226 * 隐藏菜单 227 */ 228 private void hideMenu() { 229 230 mStatus = STATUS_NORMAL; 231 updateScrollX(-mMaxScrollDistance); 232 } 233 234 private void updateScrollX(float distance) { 235 236 float dx = mScrollX + distance; 237 if (dx < mMinScrollDistance) { 238 239 dx = mMinScrollDistance; 240 } else if (dx > mMaxScrollDistance) { 241 242 dx = mMaxScrollDistance; 243 } 244 mScrollX = dx; 245 smoothScrollTo((int) dx, 0); 246 } 247 248 private void smoothScrollTo(int fx, int fy) { 249 int dx = fx - mScroller.getFinalX(); 250 int dy = fy - mScroller.getFinalY(); 251 smoothScrollBy(dx, dy); 252 } 253 254 private void smoothScrollBy(int dx, int dy) { 255 mScroller.startScroll(mScroller.getFinalX(), mScroller.getFinalY(), dx, dy); 256 invalidate(); 257 } 258 259 @Override 260 public void computeScroll() { 261 if (mScroller.computeScrollOffset()) { 262 scrollTo(mScroller.getCurrX(), mScroller.getCurrY()); 263 postInvalidate(); 264 } 265 super.computeScroll(); 266 } 267 268 public void setOnItemClickListener(OnItemClickListener listener) { 269 mOnItemClickListener = listener; 270 } 271 272 public interface OnItemClickListener { 273 void onItemClick(View view, int index, boolean isCenterView); 274 } 275 }
布局文件 swip_delete.xml
1 <?xml version="1.0" encoding="utf-8"?> 2 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 3 android:layout_width="match_parent" 4 android:layout_height="match_parent" 5 android:orientation="vertical"> 6 7 <TextView 8 android:id="@+id/tv" 9 android:layout_width="2000dp" 10 android:layout_height="50dp" 11 android:gravity="center_vertical" 12 android:padding="10dp" 13 android:text="侧滑删除菜单" /> 14 15 <com.example.administrator.technologystackapp.activities.custom.Swipe2DeleteViewGroup 16 android:id="@+id/swipe2delete" 17 android:layout_width="match_parent" 18 android:layout_height="80dp" 19 android:background="@android:color/white"> 20 21 <TextView 22 android:layout_width="match_parent" 23 android:layout_height="match_parent" 24 android:gravity="center" 25 android:text="主布局" /> 26 27 <TextView 28 android:layout_width="match_parent" 29 android:layout_height="match_parent" 30 android:background="@android:color/holo_green_light" 31 android:gravity="center" 32 android:text="置顶" 33 android:textColor="@android:color/white" /> 34 35 <TextView 36 android:layout_width="match_parent" 37 android:layout_height="match_parent" 38 android:background="@android:color/holo_green_light" 39 android:gravity="center" 40 android:text="测试" 41 android:textColor="@android:color/white" 42 android:visibility="gone" /> 43 44 <TextView 45 android:layout_width="match_parent" 46 android:layout_height="match_parent" 47 android:background="@android:color/holo_red_light" 48 android:gravity="center" 49 android:text="删除" 50 android:textColor="@android:color/white" /> 51 52 </com.example.administrator.technologystackapp.activities.custom.Swipe2DeleteViewGroup> 53 </LinearLayout>
MainActivity.java
1 import android.os.Bundle; 2 import android.view.View; 3 import android.widget.TextView; 4 import android.widget.Toast; 5 6 import com.example.administrator.technologystackapp.R; 7 import com.example.administrator.technologystackapp.activities.activity.manager.BaseActivity; 8 import com.example.administrator.technologystackapp.activities.custom.Swipe2DeleteViewGroup; 9 10 public class ActivitySwipe2delete extends BaseActivity { 11 12 private Swipe2DeleteViewGroup mSwipe2Delete; 13 14 @Override 15 protected void onCreate(Bundle savedInstanceState) { 16 super.onCreate(savedInstanceState); 17 setContentView(R.layout.swip_delete); 18 mSwipe2Delete = (Swipe2DeleteViewGroup) findViewById(R.id.swipe2delete); 19 mSwipe2Delete.setOnItemClickListener(new Swipe2DeleteViewGroup.OnItemClickListener() { 20 @Override 21 public void onItemClick(View view, int index, boolean isCenterView) { 22 if (view instanceof TextView) { 23 TextView textView = (TextView) view; 24 String str = textView.getText().toString(); 25 Toast.makeText(ActivitySwipe2delete.this, String.format("%s , isCenterView: %s", str, isCenterView), Toast.LENGTH_SHORT).show(); 26 } 27 } 28 }); 29 } 30 31 }
鸣谢
源代码是参考了CSDN大神的思路写出来的,但是他的博客地址,找不到了╮( ̄▽ ̄")╭
不过感谢一下这位大神,代码我已经贡献出来了,各位看官请自便。