Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> HorizontalScrollView仿QQ側滑刪除

HorizontalScrollView仿QQ側滑刪除

編輯:關於Android編程

效果:

\

需求:

不論什麼領域,在模仿一個東西的時候,我們首先要對它進行需求提取,這樣才能保證做到”惟妙惟肖”。通過對QQ側滑功能的分析,提取出了以下需求:

每個Item都可以側滑,根據Item類型的不同,側滑後顯示的菜單項也不同(聯系人/群組的菜單有:置頂,標為已讀,刪除, 通知類消息展示的菜單只有置頂和刪除);側滑的過程中,如果滑動距離超過第一個菜單的寬度,抬起手指時會顯示全部的菜單,即Item會滑動到最左端;在向右滑動關閉菜單的過程中,如果滑動距離超過最後一個菜單的寬度,抬起手指時會關閉全部菜單, 即Item會恢復至正常展示狀態;如果Item的菜單呈展開狀態,則點擊此Item或按下其他Item,當前的Item的菜單將會關閉;如果沒有Item的菜單呈展開狀態,點擊Item時將進入聊天頁面;觀察Item滑動的過程,發現其是勻速滑動, 而不是快速移動; 不能同時滑動多個Item;

通過對需求的分析,首先會想到HorizontalScrollView, 當然,重寫Item的RooView的onTouchListener()也可以實現,但是普通的View只有scrollTo()和scrollBy()方法, 只能快速移動而不能勻速移動,導致滑動的過程很生硬。所以我們使用HorizontalScrollView來實現我們的效果。

* 布局:*

根布局其實沒什麼內容,就是一個ListView,這樣就不貼代碼了, 下面我們主要展示一下Item的布局內容:



    
        
            
            
                
                
            
            
        
        
        
        
    

在使用HorizontalScrollView的時候有以下幾點需要注意:

它和ScrollView一樣,只能有一個子布局,且一般為LinearLayout;只有內容的寬度超過屏幕的寬度HorizontalScrollView才可以滑動; match_parent會失效。默認情況下HorizontalScrollView使用match_parent,如果內容沒有占滿屏幕寬度,則HorizontalScrollView的寬度不會達到屏幕的寬度,要想讓HorizontalScrollView和屏幕寬度一樣,需要添加 android:fillViewport=”true”屬性。同樣其子布局的match_parent也會失效,在這個布局中,id為content_layout的LinearLayout寬度為match_parent, 剛開始我認為它已經占滿了整個屏幕的寬度,然後後面的菜單按鍵就會在屏幕之外,這樣整個布局的跨度超過了屏幕寬度,HorizontalScrollView就可以自動滑動了,結果是我想多了;

實現:

step1. 讓HorizontalScrollView滑動起來

加載上面的布局,然後設置給ListView設置了布局之後發現HorizontalView根本不能滑動,問題當然很明確,Item布局中match_paren失效,既然布局中不能設置,那我們只能動態設置了,通過以下代碼設置Item中展示內容的布局寬度為屏幕寬度:

// 獲取內容展示布局的布局參數
LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) holder.contentLayout.getLayoutParams();
// 設置寬度為屏幕寬度
params.width = getResources().getDisplayMetrics().widthPixels;
// 重設布局參數
holder.contentLayout.setLayoutParams(params);

再次運行發現HorizontalScrollView可以滑動了,看起來效果還不錯,但這只是一個開始。仔細分析一下上面其他的需求,我們發現即便使用HorizontalScrollView, 卻依然需要重寫onTouchListener()才能實現。接下來我們一步一步來實現其他的需求:

step2. 不要在路上逗留

需求2和需求3主要是對滑動距離的控制, 那麼只要我們在手指彈起的時候對滑動距離進行判斷,就可以很好地對其進行控制。代碼實現如下:

    // 獲取滑動的距離
    float distance = Math.abs(event.getX() - downX);
    if (distance > 0 && distance < dpToPx(70)) { // 如果滑動距離在70dp內(Button寬度)則回復至原來的位置
        v.post(new Runnable() {
            @Override
            public void run() {
                // 如果是向右滑動,則依然恢復至菜單展開狀態
                if (isRight) {
                    ((HorizontalScrollView) view).fullScroll(View.FOCUS_RIGHT);
                } else {
                    ((HorizontalScrollView)view).fullScroll(View.FOCUS_LEFT);
                }
            }
        });
    } else { // 滑動距離超過70dp(Button寬度)則完全展開菜單或關閉菜單
        v.post(new Runnable() {
            @Override
            public void run() {
                if (isRight) {
                    ((HorizontalScrollView) view).fullScroll(View.FOCUS_LEFT);
                } else {
                    lastPosition = position;
                    ((HorizontalScrollView)view).fullScroll(View.FOCUS_RIGHT);
                }
            }
        });
    }

fullScroll()用來滑動至兩端,View.FOCUS_LEFT表示滑動至最左端,即恢復至正常顯示狀態,View.FOCUS_RIGHT表示滑動至最右段,即展開菜單。但是直接使用fullScroll()是不起作用的,因為fullScroll()的操作是異步的,系統並不會等待fullScroll()執行完成,所以我們需要使用post將其添加到消息隊列中(因為Android是通過消息隊列來實現同步的)同步操作。

step3. 出來混,早晚要回去的

有菜單展開的情況,那在某種操作下必然要恢復正常,即需求4的描述。如果點擊已展開的Item,則關閉此Item,如果按下其他的Item,則關閉已展開的Item,通過對需求的理解,我們可以得出需要處理點擊事件和按下事件。具體代碼如下:

// 處理按下其他Item則關閉已展開的Item的情況(lastPosition表示已展開的Item position)
if (lastPosition != -1 && lastPosition != position) {
    // 獲取已展開的Item的RootView
    View openedItemView = getViewByPosition(listView, lastPosition);
    if (openedItemView != null) {
        final HorizontalScrollView horizontalScrollView =                       ((HorizontalScrollView)openedItemView.findViewById(R.id.horizontal_scrollview));
        // 將已展開的Item置位
        horizontalScrollView.smoothScrollTo(0, 0);
    }
}

需要注意的是:smoothScrollTo()是同步操作,直接使用就可以。這裡需要說明一下獲取ListView指定position的ItemView的實現:

private View getViewByPosition(ListView listView, int position) {
        // 獲取當前可見的第一個Item的position
        int firstItemPos = listView.getFirstVisiblePosition();
        // 獲取最後一個可見的Item的position
        int lastItemPos = firstItemPos + listView.getChildCount() - 1;
        if (position < firstItemPos || position > lastItemPos) {
            return listView.getAdapter().getView(position, null, listView);
        } else {
            int childIndex = position - firstItemPos;
            return listView.getChildAt(childIndex);
        }
    }

剛開始獲取指定position的ItemView的時候使用了listView.getChildAt(childIndex), 結果在滑動到下一頁的時候,點擊Item就出現空指針的情況,看了下getChildAt()函數的源碼,發現其返回的是只當前頁可見的Item, 當然, getChildCount()返回也是當前頁可見的Item的數量。

處理完了第一種情況,接下來我們處理點擊已展開的Item的情況,這個問題其實很明顯,我們只需要在UP事件中處理即可:

    // 獲取滑動距離(區分滑動和點擊)
    float distance = Math.abs(event.getX() - downX);
    if (distance == 0.0) {
        //點擊已展開的Item的情況
        if (lastPosition == position) {
            v.post(new Runnable() {
                @Override
                public void run() {
                    ((HorizontalScrollView)view).fullScroll(View.FOCUS_LEFT);
                    lastPosition = -1;
                }
            });
        } else if (lastPosition == -1) {
            // 沒有Item展開,點擊時直接響應點擊事件
           Toast.makeText(MainActivity.this, "觸發了點擊事件", Toast.LENGTH_SHORT).show();
        } else {
            // 對按下其他Item導致已展開的Item關閉的情況,對lastPosition進行置位
            lastPosition = -1;
        }
    }

step4. 有條不紊

現在可以嘗試我們的滑動刪除了,但是發現竟然支持可以多點觸控,即可以側滑多個Item。So easy! 只要我們關閉listView的多點觸控即可解決此問題。在父布局的ListView中添加如下屬性即可:

android:splitMotionEvents="false"

至此,我們通過HorizontalScrollView實現了QQ側滑刪除的全部需求(通過不同的Item加載不同的菜單很簡單,根據類型對菜單項進行顯示與隱藏即可)。

源碼:

為了縮小文章的篇幅,在此我們只展示Adapter的getView的代碼:

@Override
    public android.view.View getView(final int position, android.view.View convertView, final ViewGroup parent) {
        ViewHolder holder;
        if (convertView == null) {
            holder = new ViewHolder();
            convertView = getLayoutInflater().inflate(R.layout.item_layout, parent, false);
            holder.icon = (ImageView) convertView.findViewById(R.id.icon);
            holder.nameText = (TextView) convertView.findViewById(R.id.name_text);
            holder.contentText = (TextView) convertView.findViewById(R.id.content_text);
            holder.timeText = (TextView) convertView.findViewById(R.id.time_text);
            holder.contentLayout = (LinearLayout) convertView.findViewById(R.id.content_layout);
            holder.horizontalScrollView = (HorizontalScrollView) convertView.findViewById(R.id.horizontal_scrollview);
            convertView.setTag(holder);
        } else {
            holder = (ViewHolder) convertView.getTag();
        }

        LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) holder.contentLayout.getLayoutParams();
        params.width = getResources().getDisplayMetrics().widthPixels;
        holder.contentLayout.setLayoutParams(params);
        holder.icon.setImageResource(data.get(position).icon);
        holder.nameText.setText(data.get(position).name);
        holder.contentText.setText(data.get(position).content);
        holder.timeText.setText(data.get(position).time);

        holder.horizontalScrollView.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                final View view = v;
                switch (event.getAction()) {
                    case MotionEvent.ACTION_DOWN:
                        downX = event.getX();
                        if (lastPosition != -1 && lastPosition != position) {
                            View openedItemView = getViewByPosition(listView, lastPosition);
                            if (openedItemView != null) {
                                final HorizontalScrollView horizontalScrollView = ((HorizontalScrollView)openedItemView.findViewById(R.id.horizontal_scrollview));
                                horizontalScrollView.smoothScrollTo(0, 0);
                            }
                        }
                        break;
                    case MotionEvent.ACTION_MOVE:
                        if (event.getX() > lastXOffset) {
                            isRight = true;
                        } else {
                            isRight = false;
                        }
                        lastXOffset = event.getX();
                        break;
                    case MotionEvent.ACTION_UP:
                        float distance = Math.abs(event.getX() - downX);
                        if (distance == 0.0) {
                            if (lastPosition == position) {
                                v.post(new Runnable() {
                                    @Override
                                    public void run() {
                                        ((HorizontalScrollView)view).fullScroll(View.FOCUS_LEFT);
                                        lastPosition = -1;
                                    }
                                });
                            } else if (lastPosition == -1) {
                               Toast.makeText(MainActivity.this, "觸發了點擊事件", Toast.LENGTH_SHORT).show();
                            } else {
                                lastPosition = -1;
                            }
                        } else if (distance > 0 && distance < dpToPx(70)) {
                            v.post(new Runnable() {
                                @Override
                                public void run() {
                                    if (isRight) {
                                        ((HorizontalScrollView) view).fullScroll(View.FOCUS_RIGHT);
                                    } else {
                                        ((HorizontalScrollView)view).fullScroll(View.FOCUS_LEFT);
                                    }
                                }
                            });
                        } else {
                            v.post(new Runnable() {
                                @Override
                                public void run() {
                                    if (isRight) {
                                        ((HorizontalScrollView) view).fullScroll(View.FOCUS_LEFT);
                                    } else {
                                        lastPosition = position;
                                        ((HorizontalScrollView)view).fullScroll(View.FOCUS_RIGHT);
                                    }
                                }
                            });
                        }
                        break;
                    default:
                        break;
                }

                return false;
            }
        });

        return convertView;
    }
}

 

   
  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved