仿QQ主界面滑动删除置顶
主界面的布局:
向左滑动之后出现“删除”和“置顶”两个按钮。
再点击列表项一下后会返回到原来的状态。
思路:
使用scroller类实现一个自定义View,并将自定义View作为ListView的子项布局呈现出来,功能在自定义View中实现。
代码实现:
先实现自定义View。 自定义View含有三个部分,1个TextView,2个Button,两个Button覆盖在TextView上方,所以让自定义View继承自RelativeLayout比较合适。
public class SlideMenuView extends RelativeLayout { private int mScreenWidth; private int mScreenHeight; private Context mContext; private int mDeleteWidth; private int mDeleteHeight; private int state = 0; private static final int START = 0; private static final int PULL = 1; private static final int RELEASE = 2; private int mStartX; private int mStartY; private int mTotalMoveX; private Scroller mScroller; private boolean isShowing = false; private OnClickListener mOnDeleteClickListener; private OnClickListener mOnTopClickListener; private String name; private TextView mTvName; private Button mBtnDelete; private Button mBtnTop; private int start_x; private int start_y; public SlideMenuView(Context context) { super(context); init(context); } public SlideMenuView(Context context, AttributeSet attrs) { super(context, attrs); init(context); } public SlideMenuView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(context); } private void init(Context mContext) { DisplayMetrics metrics = mContext.getResources().getDisplayMetrics(); mScreenWidth = metrics.widthPixels; mScreenHeight = metrics.heightPixels; mScroller = new Scroller(mContext); this.mContext = mContext; } public void setOnDeleteClickListener(OnClickListener listener) { this.mOnDeleteClickListener = listener; } public void setOnTopClickListener(OnClickListener listener) { this.mOnTopClickListener = listener; } public void setTextName(String name) { this.name = name; } //由于是自定义LinearLayout,应该覆盖这个方法来实现自己对控件宽高的安排 @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); //if语句用来判断子控件的数量,也可以通过getChildAt来获取子控件 if (getChildCount() > 0) { for (int n = 0; n < getChildCount(); n++) { View view = getChildAt(n); measureChild(view, widthMeasureSpec, heightMeasureSpec); } } } //由于布局文件中包含有三个子控件,在自定义View中不要使用findViewById来寻找子控件。 @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { super.onLayout(changed, l, t, r, b); if (getChildCount() > 0) { for (int n = 0; n < getChildCount(); n++) { View view = getChildAt(n); if (n == 0) { view.layout(l, t, view.getMeasuredWidth(), b); } else if (n == 1) { mDeleteHeight = view.getMeasuredHeight(); mDeleteWidth = view.getMeasuredWidth(); view.layout(mScreenWidth, t, mScreenWidth + view.getMeasuredWidth(), b); } else if (n == 2) { view.layout(mScreenWidth + view.getMeasuredWidth(), t, mScreenWidth + view.getMeasuredWidth() * 2, b); } } } //跟布局代码相关 mTvName = (TextView) getChildAt(0); mBtnDelete = (Button) getChildAt(1); mBtnTop = (Button) getChildAt(2); if (mOnDeleteClickListener != null) { mBtnDelete.setOnClickListener(mOnDeleteClickListener); } if (mOnTopClickListener != null) { mBtnTop.setOnClickListener(mOnTopClickListener); } if (name != null) { mTvName.setText(name); } } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); if (getChildCount() > 0) { for (int n = 0; n < getChildCount(); n++) { View view = getChildAt(n); drawChild(canvas, view, 50); } } } @Override public boolean onTouchEvent(MotionEvent ev) { switch (ev.getAction()) { case MotionEvent.ACTION_DOWN://记录坐标点 mStartX = (int) ev.getX(); mStartY = (int) ev.getY(); start_x = (int) ev.getX(); start_y = (int) ev.getY(); state = PULL; break; case MotionEvent.ACTION_MOVE: //不让listView拦截事件 if (Math.abs(ev.getX() - start_x) > Math.abs(ev.getY() - start_y)) { requestDisallowInterceptTouchEvent(true);//不让父控件去处理这个方法 } else { requestDisallowInterceptTouchEvent(false); } if (state == PULL) { mTotalMoveX += ((ev.getX() - mStartX)); if (!isShowing && mTotalMoveX < 0) { scrollBy((int) (mStartX - ev.getX()), 0); } mStartX = (int) ev.getX(); } break; case MotionEvent.ACTION_UP://在抬手的时候再实现滑动操作 if (mTotalMoveX < 0) { if (!isShowing) { if (Math.abs(mTotalMoveX) > mDeleteWidth * 2 / 3) { startScroll(-mTotalMoveX, 0, mDeleteWidth * 2 + mTotalMoveX, 0, 500); isShowing = true; } else { startScroll(-mTotalMoveX, 0, mTotalMoveX, 0, 500); isShowing = false; } } } else { if (isShowing) { startScroll(mDeleteWidth * 2, 0, -mDeleteWidth * 2, 0, 500); isShowing = false; } } mTotalMoveX = 0; state = START; break; } return true; } public void startScroll(int startX, int startY, int dx, int dy, int time) { mScroller.startScroll(startX, startY, dx, dy, time); postInvalidate();//调用computeScroll()函数 } /** * 完成实际的滚动 */ @Override public void computeScroll() { if (mScroller.computeScrollOffset()) {//如果滑动操作还没有完成,则会返回true scrollTo(mScroller.getCurrX(), 0); invalidate(); } } }
然后再MainActivity中给ListView填充自定义View。
public class MainActivity extends Activity { private ListView mListView; private MyAdapter mAdapter; private ArrayList<String> list; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initList(); mListView = (ListView) findViewById(R.id.listView); mAdapter = new MyAdapter(this, list); mListView.setAdapter(mAdapter); } public void initList() { list = new ArrayList<>(); list.add("测试1"); list.add("测试2"); list.add("测试3"); list.add("测试4"); list.add("测试5"); list.add("测试6"); list.add("测试7"); list.add("测试8"); list.add("测试9"); list.add("测试10"); } private class MyAdapter extends BaseAdapter { private Context mContext; private ArrayList<String> list; private LayoutInflater mInflater; public MyAdapter(Context mContext, ArrayList<String> list) { this.mContext = mContext; this.list = list; mInflater = LayoutInflater.from(mContext); } @Override public int getCount() { if (list != null && list.size() > 0) { return list.size(); } return 0; } @Override public Object getItem(int position) { return list.get(position); } @Override public long getItemId(int position) { return position; } @Override public View getView(int position, View convertView, ViewGroup parent) { Holder holder; if (convertView == null) { holder = new Holder(); convertView = mInflater.inflate(R.layout.item_test, null); holder.view = (SlideMenuView) convertView.findViewById(R.id.view); convertView.setTag(holder); } else { holder = (Holder) convertView.getTag(); holder.view.setTextName(""); } String item = (String) getItem(position); if (item != null && !"".equals(item)) { holder.view.setTextName(item); } return convertView; } /** * Holder模式要解决的是性能问题: 场景:在我们的Adapter中每次都要从row中通过findViewById来找到子控件,然后设置值。 如果row的布局比较复杂,或者row的数目特别多。这个查找就要不断发生。从而导致性能问题。 方案:在row第一次被构建出来的时候,调用findViewById, 通过Holder对象存储起来, 然后把Holder对象通过row.setTag方法,直接缓存在row上。这样下次就不用在查找了。 */ public class Holder { private SlideMenuView view; } } }
最后布局文件(2个):
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content"> <ListView android:id="@+id/listView" android:layout_width="match_parent" android:layout_height="match_parent" android:divider="@color/line" android:dividerHeight="1dp" android:cacheColorHint="@android:color/transparent"> </ListView> </RelativeLayout>
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <RelativeLayout android:layout_width="match_parent" android:layout_height="40dp"> <TextView android:layout_width="wrap_content" android:layout_height="40dp" android:text="大神在此" android:textSize="16dp" android:gravity="center_vertical" /> <Button android:layout_width="wrap_content" android:layout_height="40dp" android:text="删除" android:textSize="16dp" android:background="@android:color/transparent" /> <Button android:layout_width="wrap_content" android:layout_height="40dp" android:text="置顶" android:textSize="16dp" android:background="@android:color/transparent" /> </RelativeLayout> </RelativeLayout>
完成。