這個效果的完成主要分為兩個部分 1. 自定義view作為listview的列表項 一個view裡面包括 顯示頭像,名字,消息內容等的contentView和滑動才能顯示出來的刪除,置頂的右邊菜單menuView 在手指移動的時候同時改變這兩個視圖的位置 2. 重寫listview 判斷item向左還 ...
這個效果的完成主要分為兩個部分
自定義view作為listview的列表項 一個view裡面包括 顯示頭像,名字,消息內容等的contentView和滑動才能顯示出來的刪除,置頂的右邊菜單menuView 在手指移動的時候同時改變這兩個視圖的位置
重寫listview 判斷item向左還是向右滑動 正常的滾動還是左右滑動等等 重寫onTouchEvent 進行事件分發
大致思路:
listview進行事件分發,判斷需要滑動還是滾動等狀態,如果需要滑動將事件傳遞給item進行滑動處理. 在item中控制contentView和menuView進行位置的變化完成滾動效果
重寫listview代碼
public class SlideListView extends ListView{
private SlideItem mTouchView=null;//記錄當前點擊的item View
private float mDownX;//x軸坐標
private float mDownY;//y軸坐標
private int mTouchState;//記錄點擊狀態
private int mTouchPosition;//記錄點擊位置
private static final int TOUCH_STATE_NONE=0; //按下狀態
private static final int TOUCH_STATE_X=1;//橫滑狀態
private static final int TOUCH_STATE_Y=2;//豎滑狀態
//判斷橫豎滑動的最小值
private static final int MAX_Y=5;
private static final int MAX_X=3;
public SlideListView(Context context) {
super(context);
}
public SlideListView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public SlideListView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
if (ev.getAction() != MotionEvent.ACTION_DOWN && mTouchView == null)
return super.onTouchEvent(ev);
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
//按住的item的position
int oldPosition = mTouchPosition;
//記錄位置
mDownX = ev.getX();
mDownY = ev.getY();
mTouchState = TOUCH_STATE_NONE;
//根據當前橫縱坐標點獲取點擊的item的position
mTouchPosition = this.pointToPosition((int) ev.getX(), (int) ev.getY());
//判斷當前點擊的是否和上次點擊的item是同一個,如果是同一個,並且狀態是打開了的就記錄狀態和坐標
//記錄坐標通過Item中的downX屬性
if (mTouchPosition == oldPosition && mTouchView != null && mTouchView.isOpen()) {
mTouchState = TOUCH_STATE_X;
mTouchView.onSwipe(ev);
return true;
}
//獲取當前的item的View
View currentView = getChildAt(mTouchPosition - getFirstVisiblePosition());
//如果不是同一個item 那麼點擊的話就關閉掉之前打開的item
if (mTouchView != null && mTouchView.isOpen()) {
mTouchView.smoothCloseMenu();
mTouchView = null;
return super.onTouchEvent(ev);
}
//判斷該view的類型
if (currentView instanceof SlideItem) {
mTouchView = (SlideItem) currentView;
}
if (mTouchView != null) {
mTouchView.onSwipe(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.onSwipe(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.onSwipe(ev);
//如過最後狀態是打開 那麼就重新初始化
if (!mTouchView.isOpen()) {
mTouchPosition = -1;
mTouchView = null;
}
}
ev.setAction(MotionEvent.ACTION_CANCEL);
super.onTouchEvent(ev);
return true;
}
break;
}
return super.onTouchEvent(ev);
}
}
重寫item項
view的滑動效果都是在里完成的 使用了Scroller類
關於Scroller的使用文章最後已經粘出了大神的帖子 不懂的同學可以先把Scroller的使用理解了在看這個滑動效果就很好懂了 我在這裡簡單講講
這個類的並沒有實際的完成滾動效果 它是一個計算控制項移動軌跡的輔助類,
比如說:在1秒內從位置0移動到位置100 這個類會計算出移動的數值,它並沒有完成滑動的效果,但是告訴了我們這個滑動的過程 實際的上的view移動操作在computeScroll()完成 這個方法是view的自帶方法 需要我們重寫
computeScroll方法又是怎麼情況呢 看源碼 本身是個空的 就等著我們實現 我們實際改變view位置的代碼就是在此方法內調用的
額。。。英語一般
大致意思 我們要通過Scroller實現一個滾動效果的時候 父佈局就會調用此方法來完成子視圖的位置更新
官方的描述是:當我們執行ontouch或invalidate()或postInvalidate()都會導致這個方法的執行
在此方法中不斷的獲取到移動的距離 通過view自帶的layout()方法更新view所在位置
/**
* Called by a parent to request that a child update its values for mScrollX
* and mScrollY if necessary. This will typically be done if the child is
* animating a scroll using a {@link android.widget.Scroller Scroller}
* object.
*/
public void computeScroll() {
}
public class SlideItem extends LinearLayout {
private View contentView = null; //不滑動顯示的view
private View menuView = null; //左滑顯示的view
//計算滑動 動畫效果
private Scroller mOpenScroller;
private Scroller 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 SlideItem(Context context) {
super(context);
}
public SlideItem(Context context, AttributeSet attrs) {
super(context, attrs);
}
public SlideItem(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public void setContentView(View contentView, View rightView){
this.contentView = contentView;
this.menuView = rightView;
//初始化mColoseScroller和mOpenScroller
mCloseScroller=new Scroller(getContext());
mOpenScroller = new Scroller(getContext());
initView();
}
//child view的佈局參數設定好後 添加到parent view裡面
private void initView() {
//這是設置寬和高
LayoutParams contentParams = new LayoutParams
(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
LayoutParams rightParams=new LayoutParams
(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);
contentView.setLayoutParams(contentParams);
contentView.setPadding(10,10,10,10);
menuView.setLayoutParams(rightParams);
this.addView(contentView);
this.addView(menuView);
}
// 判斷是否滑出的狀態
public boolean isOpen() {
return state == STATE_OPEN;
}
/**
* 供listView調用 進行視圖的移動 listView判斷狀態 什麼情況下左滑
* @param event
* @return
*/
public boolean onSwipe(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 += menuView.getWidth();
}
//移動
move(dis);
break;
case MotionEvent.ACTION_UP:
//當滑到右邊視圖一半的距離 自動滑進滑出
if ((downX - event.getX()) > (menuView.getWidth() / 2)) {
smoothOpenMenu();
} else {
smoothCloseMenu();
return false;
}
break;
}
//消費掉事件
return true;
}
/**
* 視圖重新繪製時調用
*/
@Override
public void computeScroll() {
if (state == STATE_OPEN) {
//computeScrollOffset滑動是否結束
if (mOpenScroller.computeScrollOffset()) {
move(mOpenScroller.getCurrX());
postInvalidate();
}
} else {
if (mCloseScroller.computeScrollOffset()) {
move(mBaseX - mCloseScroller.getCurrX());
postInvalidate();
}
}
}
/**
* 移動視圖
* @param dis
*/
private void move(int dis) {
//這兩個判斷是為了保證 不要把視圖移動過多 導致視圖偏移
if (dis > menuView.getWidth()) {
dis = menuView.getWidth();
}
if (dis < 0) {
dis = 0;
}
//view.layout()控制view相對於其父佈局的位置 在觸發移動的時候調用不斷改變位置 完成實際的滑動效果
contentView.layout(-dis, contentView.getTop(), contentView.getWidth() - dis, getMeasuredHeight());
menuView.layout(contentView.getWidth() - dis, menuView.getTop(), contentView.getWidth() + menuView.getWidth() - dis, menuView.getBottom());
}
/**
* 滑動關閉
* contentView.getLeft() 與其父視圖的相對位置
*/
public void smoothCloseMenu() {
state = STATE_CLOSE;
mBaseX = -contentView.getLeft();
mCloseScroller.startScroll(0, 0, mBaseX, 0, 350);
postInvalidate();
}
/**
* 滑動打開
*/
public void smoothOpenMenu() {
state = STATE_OPEN;
mOpenScroller.startScroll(-contentView.getLeft(), 0, menuView.getWidth(), 0, 350);
postInvalidate();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if(menuView != null)
menuView.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) {
//確保centerView menuView的顯示位置
if(contentView != null)
contentView.layout(0, 0, getMeasuredWidth(), contentView.getMeasuredHeight());
if(menuView != null)
menuView.layout(getMeasuredWidth(), 0, getMeasuredWidth() + menuView.getMeasuredWidth(), contentView.getMeasuredHeight());
}
}
適配器
public class SlideAdapter extends BaseAdapter implements View.OnClickListener{
private List<String> dataList;
private Context context;
private LayoutInflater inflater;
public SlideAdapter(Context context, List<String> dataList) {
this.context = context;
this.dataList = dataList;
this.inflater=LayoutInflater.from(context);
}
@Override
public int getCount() {
return 5;
}
@Override
public Object getItem(int position) {
return null;
}
@Override
public long getItemId(int position) {
return 0;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder=null;
if (convertView==null){
View content=inflater.inflate(R.layout.adapter_item_content,null);
View menu=inflater.inflate(R.layout.adapter_item_menu,null);
holder=new ViewHolder(content,menu);
SlideItem slideItem=new SlideItem(context);
slideItem.setContentView(content,menu);
convertView=slideItem;
convertView.setTag(holder);
}else {
holder= (ViewHolder) convertView.getTag();
}
holder.itemTvDelete.setOnClickListener(this);
holder.itemTvNoRead.setOnClickListener(this);
holder.itemTvToTop.setOnClickListener(this);
return convertView;
}
class ViewHolder{
TextView itemTvToTop;
TextView itemTvNoRead;
TextView itemTvDelete;
public ViewHolder(View center,View menu) {
this.itemTvToTop = (TextView) menu.findViewById(R.id.item_to_top);
this.itemTvNoRead = (TextView) menu.findViewById(R.id.item_no_read);
this.itemTvDelete = (TextView) menu.findViewById(R.id.item_delete);
}
}
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.item_no_read:
Toast.makeText(context,"標為未讀",Toast.LENGTH_SHORT).show();
break;
case R.id.item_to_top:
Toast.makeText(context,"置頂了熬",Toast.LENGTH_SHORT).show();
break;
case R.id.item_delete:
Toast.makeText(context,"刪除啦",Toast.LENGTH_SHORT).show();
break;
}
}
}
參考文檔:
SwipeMenuListView github上的實現此效果的開源項目
Scroller的使用