側滑原理:
側滑是通過手勢判斷,將中間的view滑出去,將右邊的view滑進來,當滑動的距離不夠完全顯示右邊的view的時候執行平滑函數,自動將右邊的view滑進去,並改變滑動狀態爲open。所以需要通過xml定義兩個view,一個作爲默認顯示的view,一個作爲隱藏(需要滑入)的view。
先從簡單練起:
開始我做了一個簡單的demo,沒有涉及listview,只是寫了簡單的一個滑入滑出的效果。
public class ScrollerLinearLayout extends LinearLayout {
private View centerView = null; //不滑動顯示的view
private View rightView = null; //左滑顯示的view
//用這兩個可以實現滑動效果
private ScrollerCompat mOpenScroller;
private ScrollerCompat mCloseScroller;
private int downX; //開始按下的位置
//記錄狀態
private int state = STATE_CLOSE;
private static final int STATE_CLOSE = 0;
private static final int STATE_OPEN = 1;
private int mBaseX;
public ScrollerLinearLayout(Context context) {
super(context);
}
public ScrollerLinearLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ScrollerLinearLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public void setContentView(View centerView, View rightView){
this.centerView = centerView;
this.rightView = rightView;
//初始化mColoseScroller和mOpenScroller
mCloseScroller = ScrollerCompat.create(getContext());
mOpenScroller = ScrollerCompat.create(getContext());
initView();
}
//child view的佈局參數設定好後 添加到parent view裏面
private void initView() {
// setLayoutParams(new AbsListView.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT));
setLayoutParams(new LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
//這也是設置寬和高
LinearLayout.LayoutParams contentParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT);
centerView.setLayoutParams(contentParams);
rightView.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.MATCH_PARENT));
//將這兩個佈局都add到這個view中
addView(centerView);
addView(rightView);
}
// 判斷是否滑出的狀態
public boolean isOpen() {
return state == STATE_OPEN;
}
/**
* 這裏的函數是由 listview來控制的。
* listview來監聽左滑,並判斷是否需要將rightview 顯示出來。
* (因爲不是所有左滑都要畫出rightview,還需要判斷listview的其他item的狀態不是STATE_OPEN 狀態
* ,所以由listview來控制自己item的rightview是否左滑最合理,這個函數目的就是讓listview來調用)
* 反之右滑亦然
*
* @param event
* @return
*/
public boolean onSwipeLeft(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
downX = (int) event.getX();
break;
case MotionEvent.ACTION_MOVE:
int dis = (int) (downX - event.getX());
if (state == STATE_OPEN) {
dis += rightView.getWidth();
}
//手指移動多少距離,rightview也移動多少距離
swipe(dis);
break;
case MotionEvent.ACTION_UP:
if ((downX - event.getX()) > (rightView.getWidth() / 2)) {
smoothOpenMenu(); //自動滑出來
} else {
smoothCloseMenu(); //自動滑進去
return false;
}
break;
}
//消費掉事件
return true;
}
@Override
public void computeScroll() {
if (state == STATE_OPEN) {
if (mOpenScroller.computeScrollOffset()) {
swipe(mOpenScroller.getCurrX());
postInvalidate();
}
} else {
if (mCloseScroller.computeScrollOffset()) {
swipe(mBaseX - mCloseScroller.getCurrX());
postInvalidate();
}
}
}
private void swipe(int dis) {
if (dis > rightView.getWidth()) {
dis = rightView.getWidth();
}
if (dis < 0) {
dis = 0;
}
centerView.layout(-dis, centerView.getTop(), centerView.getWidth() - dis, getMeasuredHeight());
rightView.layout(centerView.getWidth() - dis, rightView.getTop(), centerView.getWidth() + rightView.getWidth() - dis, rightView.getBottom());
}
public void smoothCloseMenu() {
state = STATE_CLOSE;
mBaseX = -centerView.getLeft();
mCloseScroller.startScroll(0, 0, mBaseX, 0, 350);
postInvalidate();
}
public void smoothOpenMenu() {
state = STATE_OPEN;
mOpenScroller.startScroll(-centerView.getLeft(), 0, rightView.getWidth(), 0, 350);
postInvalidate();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if(rightView != null)
rightView.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY));
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if(centerView != null)
centerView.layout(0, 0, getMeasuredWidth(), centerView.getMeasuredHeight());
if(rightView != null)
rightView.layout(getMeasuredWidth(), 0, getMeasuredWidth() + rightView.getMeasuredWidth(), centerView.getMeasuredHeight());
}
@Override
public boolean onTouchEvent(MotionEvent event) {
onSwipeLeft(event);
return true;
}
}
MainActivity部分:
private void initScrollerLayout() {
View centerView1 = LayoutInflater.from(this).inflate(R.layout.view_framelayout_01, null);
View rightView1 = LayoutInflater.from(this).inflate(R.layout.view_framelayout_02, null);
ScrollerLinearLayout scrollerLinearLayout1 = (ScrollerLinearLayout)super.findViewById(R.id.activity_listview_scroller_layout01);
scrollerLinearLayout1.setContentView(centerView1, rightView1);
View centerView2 = LayoutInflater.from(this).inflate(R.layout.view_framelayout_01, null);
View rightView2 = LayoutInflater.from(this).inflate(R.layout.view_framelayout_02, null);
ScrollerLinearLayout scrollerLinearLayout2 = (ScrollerLinearLayout)super.findViewById(R.id.activity_listview_scroller_layout02);
scrollerLinearLayout2.setContentView(centerView2, rightView2);
}
XML部分:
view_framelayout_01
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal" android:layout_width="match_parent"
android:gravity="center_vertical"
android:layout_height="match_parent">
<com.best.android.listviewactivity.CircleImageView
android:layout_width="50dp"
android:layout_height="50dp" />
<LinearLayout
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<RelativeLayout
android:layout_margin="4dp"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<TextView
android:text="android交流羣"
android:textSize="14dp"
android:layout_alignParentLeft="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:text="15:34"
android:layout_alignParentRight="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</RelativeLayout>
<RelativeLayout
android:layout_margin="4dp"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<TextView
android:text="Emma加入羣"
android:textColor="#666666"
android:textSize="14dp"
android:layout_alignParentLeft="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:text="99+"
android:gravity="center"
android:textColor="#fff"
android:background="@mipmap/green_bg"
android:layout_alignParentRight="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</RelativeLayout>
</LinearLayout>
</LinearLayout>
view_framelayout_02:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal" android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:background="#444444"
android:gravity="center"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<TextView
android:text="置頂"
android:textColor="#fff"
android:textSize="16sp"
android:layout_margin="20dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
<LinearLayout
android:id="@+id/view_framelayout_02_ll_delete"
android:background="#ee2222"
android:gravity="center"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<TextView
android:text="刪除"
android:textColor="#fff"
android:textSize="16sp"
android:layout_margin="20dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
</LinearLayout>
基本側滑已經實現,但是不是我們最終想要的效果,所以接下來我們要加入listview
與listview關聯:
首先更改上面代碼的這個部分(將第一句取消註釋,第二句註釋掉)
// setLayoutParams(new AbsListView.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT));
setLayoutParams(new LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
並將ScrollerLinearLayout裏面的ontouchevent函數去掉,因爲現在事件監聽將在listview裏面做判斷。
主要難點在自定義listview裏面的ontouchEvent方法 這裏詳細講裏面的邏輯。。。。。
首先梳理下思路: 在action.down的時候我們都需要做哪些呢?
1.記錄下按下的位置。
2.記錄下是listview裏面的哪個item
3.狀態要改變
4.獲取當前的item的view對象。
5.判斷item是否和上次點擊的item相同,如果不相同那麼就直接讓view關閉,如果相同那麼記錄下x座標
action.move的時候我們做的事情比較簡單,記錄下移動的距離,判斷是否是x方向的移動,是的話就改變滑動狀態,並直接調用onswipeleft()執行滑動。否則就是讓listview上下滾動了。
action.up,我們就需要判斷滑動是否執行到位,不到位就調用smoothOpen方法,將沒有滑出的部分滑出,沒有滑入的部分滑入。
自定義Listview ontouch部分代碼
@Override
public boolean onTouchEvent(MotionEvent ev) {
if (ev.getAction() != MotionEvent.ACTION_DOWN && mTouchView == null)
return super.onTouchEvent(ev);
int action = MotionEventCompat.getActionMasked(ev);
// int disY;
action = ev.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
//按住的item的position
int oldPos = mTouchPosition;
//記錄位置
mDownX = ev.getX();
mDownY = ev.getY();
mTouchState = TOUCH_STATE_NONE;
//獲取點擊的item的position
mTouchPosition = pointToPosition((int) ev.getX(), (int) ev.getY());
//判斷當前點擊的是否和上次點擊的item是同一個,如果是同一個,並且狀態是打開了的就記錄狀態和座標
//mTouchView.onSwipeLeft(ev);在down方法裏就是記錄X座標的
if (mTouchPosition == oldPos && mTouchView != null && mTouchView.isOpen()) {
mTouchState = TOUCH_STATE_X;
mTouchView.onSwipeLeft(ev);
return true;
}
//獲取當前的item的View
View view = getChildAt(mTouchPosition - getFirstVisiblePosition());
//如果不是同一個item 那麼點擊的話就關閉掉之前打開的item
if (mTouchView != null && mTouchView.isOpen()) {
mTouchView.smoothCloseMenu();
mTouchView = null;
return super.onTouchEvent(ev);
}
//判斷該view的類型
if (view instanceof ScrollerLinearLayout) {
mTouchView = (ScrollerLinearLayout) view;
}
if (mTouchView != null) {
mTouchView.onSwipeLeft(ev);
}
break;
case MotionEvent.ACTION_MOVE:
float dy = Math.abs((ev.getY() - mDownY));
float dx = Math.abs((ev.getX() - mDownX));
if (mTouchState == TOUCH_STATE_X) {
if (mTouchView != null) {
//執行滑動
mTouchView.onSwipeLeft(ev);
}
return true;
} else if (mTouchState == TOUCH_STATE_NONE) {
//判斷滑動方向,x方向執行滑動,Y方向執行滾動
if (Math.abs(dy) > MAX_Y) {
mTouchState = TOUCH_STATE_Y;
} else if (dx > MAX_X) {
mTouchState = TOUCH_STATE_X;
}
}
break;
case MotionEvent.ACTION_UP:
if (mTouchState == TOUCH_STATE_X) {
if (mTouchView != null) {
mTouchView.onSwipeLeft(ev);
//如過最後狀態是打開 那麼就重新初始化
if (!mTouchView.isOpen()) {
mTouchPosition = -1;
mTouchView = null;
}
}
ev.setAction(MotionEvent.ACTION_CANCEL);
super.onTouchEvent(ev);
return true;
}
break;
}
return super.onTouchEvent(ev);
}
listview adapter部分代碼
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if(convertView==null){
View view01 = LayoutInflater.from(context).inflate(R.layout.view_framelayout_01, null);
View view02 = LayoutInflater.from(context).inflate(R.layout.view_framelayout_02, null);
LinearLayout layoutDelete = (LinearLayout)view02.findViewById(R.id.view_framelayout_02_ll_delete);
layoutDelete.setOnClickListener(new OnDeleteClickListener(position));
// convertView = new MyFrameLayout(view01, view02);
ScrollerLinearLayout scrollerLinearLayout = new ScrollerLinearLayout(context);
scrollerLinearLayout.setContentView(view01, view02);
convertView = scrollerLinearLayout;
}
return convertView;
最後效果圖:
等下拉刷新功能完成後,會將源碼一起上傳上去,不過上面都已經將的很詳細了,應該都能實現開發功能了。。。。
最後總結下:其實這個思路不僅僅是實現listview的側滑,按照這個思路來gridview,recycleview都是能實現側滑效果的,大家多多舉一反三。