Andorid分組Item頂部懸停 + 交互同步

Andorid分組Item頂部懸停 + 交互同步

需求概述

項目中某些頁面中的分組數(shù)據(jù)的頂部需要懸停,并且懸停的View要與ItemView中同樣布局的View進行操作同步,也就是相互同步兼贡。大家都知道,Android中有"The specified child already has a parent. You must call removeView() on the child's parent first."這個異常娃胆,意味著同一個View對象不能有兩個Parent遍希。我們就不能簡單粗暴的將同一個View對象添加進兩個parent了,需要另謀出路里烦。

方案選擇:

①sitckyScrollView懸停:不支持list的復用凿蒜,主線程會卡頓,pass胁黑。

②在listview的頂部覆蓋一個View废封,重新生成需要懸停的View,并做到懸停View和原View的同步丧蘸。針對listview的滑動和分組懸停漂洋,這個方案工作量太大,可行性不高。

③NestedScrollingChild和NestedScrollingParent方案刽漂,不支持分組演训,不合適。

④recyclerView + ItemDecoration方案 + View.draw(canvas) + motionEvent.offLocation():可行贝咙。

我為什么選擇第四個方案呢样悟?主要原因如下:
  
  首先我們操作的是個列表,在控制懸停的View的顯示和移動時必須要知道頂部的Item的信息庭猩,RecyclerView.ItemDecoration可以很好的解決這個問題窟她。在ItemDecoration中可以輕松獲取到RecyclerView、可見的position以及RecyclerView.Adapter中的可見View等信息眯娱,這樣我們獲取到需要懸停的View就很容易了礁苗。
  
  第二,在ItemDecoration#onDrawView( )方法中我們可以將需要懸停的View繪制出來徙缴。
  
  ItemDecoration輕松幫我們實現(xiàn)了懸停View的繪制试伙,我們只需要處理真實View與懸繪制出來的懸停View的狀態(tài)同步即可。至于如何實現(xiàn)狀態(tài)同步于样,這個問題留待后面再說明疏叨。

ItemDecoration

這里先說下ItemDecoration的實現(xiàn),它是一個接口穿剖,內(nèi)部各個方法的作用如下圖所示:


ItemDecoration內(nèi)部方法

如上所述蚤蔓,我們繪制View的時機應該是在onDrawOver方法中。

如何頂部的View

先上代碼糊余,

//獲取最頂部的ItemView
View adapterView = parent.getChildAt(0);
        if (adapterView != null) {
            //獲取需要繪制的View秀又,這里我們需要繪制的包括一個title,一個NewCHLayoutUnScroll的Header贬芥。
            //順便獲取這兩個View的高度吐辙,后面我們需要他們的高度來實現(xiàn)懸停View異動的效果
            View title = adapterView.findViewById(R.id.title);
            int titleHeight = 0;
            if (title != null && View.VISIBLE == title.getVisibility()) {
                titleHeight = title.getMeasuredHeight();
            }

            int saveCount = canvas.save();
            //設置總體偏移量,需要用到我們上邊獲取到的高度
            stickyViewHeight = titleHeight;
            if (adapterView.getBottom() < stickyViewHeight) {
                offsetY = stickyViewHeight - adapterView.getBottom();
                canvas.translate(0, -offsetY);
            }
            //渲染View
            if (title != null) {
                title.draw(canvas);
                isTitleDrawed = true;
            }
            canvas.restoreToCount(saveCount);
        } else {

        }

接下來對上述代碼進行說明:

1蘸劈、獲取最頂部的ItemView
我們知道昏苏,RecyclerView#getChildren方法可以獲取到當前所有可見的ItemView,同理威沫,RecyclerView#getChildAt(int index)就可以根據(jù)position獲取到對應位置的View贤惯,這里我們就可以通過View adapterView = parent.getChildAt(0)來獲取到最頂部的ItemView了。

2棒掠、獲取需要繪制的View
我們需要繪制在頂部的View是最頂部的ItemView的子View孵构,根據(jù)view.findviewById(id)就可以獲取到需要繪制的View了。

3烟很、為了實現(xiàn)豎直方向RecyclerView時懸停的View同步上下滑動的效果颈墅,我們需要找到懸停View顯示完全與不完全的臨界值棒假,如下圖所示:

[圖片上傳失敗...(image-129a6f-1562746137143)]

[圖片上傳失敗...(image-d96ff2-1562746137143)]

如上圖所分析,在繪制懸停View時精盅,我們可以根據(jù)懸停View的高度和最上方ItemView.getBottom( )的大小來確定懸停View繪制的offset帽哑,從而就可以實現(xiàn)懸停View在合適的時機跟隨RecyclerView滑動。

將ItemView中的狀態(tài)變化同步給懸停View叹俏。

這里說的狀態(tài)變化同步主要包括itemView中的列表左右滑動和ItemView中的title的點擊事件觸發(fā)的懸停View的狀態(tài)更新妻枕。實現(xiàn)起來其實很簡單,只需要在ItemView中更新狀態(tài)時調(diào)用下面這行代碼即可:

mRecyclerView.invalidateItemDecorations();

RecyclerView#invalidateItemDecorations( )方法會引起ItemDecoration的重繪粘驰,onDrawOver方法勢必會重新調(diào)用屡谐,所以懸停View也就會重新繪制,就會跟頂部ItemView的title保持一致蝌数。

將懸停View的事件同步給頂部的ItemView愕掏。

這一步驟是最棘手的一步,這個問題可以理解為如何將canvas繪制的View的事件同步到被繪制的View上去顶伞。首先繪制出來的懸停View并不是真正的View饵撑,它的事件默認是傳遞給RecyclerView的,即使在RecyclerView中直接攔截了這個事件唆貌,如何處理也是個問題滑潘,因為很難定位MotionEvent的實際位置。
到了這一步锨咙,我們就可以借鑒前面提到過的StickyScrollView中對繪制出的懸停View的處理方法了语卤,核心代碼如下所示:

StickyScrollView#onTouchEvent
@Override
    public boolean onTouchEvent(MotionEvent ev) {
        if (redirectTouchesToStickyView) {
            ev.offsetLocation(0, ((getScrollY() + stickyViewTopOffset) - getTopForViewRelativeOnlyChild(currentlyStickingView)));
        }

        ...
        return super.onTouchEvent(ev);
    }

核心代碼是ev.offsetLocation( ),我們看下它的源碼:

MotionEvent#offsetLocation
/**
     * Adjust this event's location.
     * @param deltaX Amount to add to the current X coordinate of the event.
     * @param deltaY Amount to add to the current Y coordinate of the event.
     */
    public final void offsetLocation(float deltaX, float deltaY) {
        if (deltaX != 0.0f || deltaY != 0.0f) {
            nativeOffsetLocation(mNativePtr, deltaX, deltaY);
        }
    }

這個方法會將MotionEvent的作用位置偏移一定的位置酪刀,也就是說會將事件傳遞到別的位置上粹舵。另外,在ViewGroup的事件分發(fā)的源碼中骂倘,也是通過MotionEvent#offsetLocation(offsetX, offsetY)來對事件進行處理的眼滤。

通過以上分析,我們可以通過MotionEvent#offsetLocation(offsetX, offsetY)方法將懸停View的MotionEvent傳遞給真實的View區(qū)域即可稠茂,唯一需要做的就是計算offsetY的值柠偶。

還有一個環(huán)節(jié)需要注意情妖,我們在哪兒獲取到這個MotionEvent睬关,如何獲取到RecyclerView.Item的事件呢?請看這兒毡证,Passing MotionEvents from RecyclerView.OnItemTouchListener to GestureDetectorCompat电爹,首先給recyclerView添加OnItemTouchListener,然后在OnItemTouchListener#onInterceptTouchEvent方法中就可以獲取到事件了料睛;獲取到事件之后我們還需要借助手勢相關的類來對事件進行處理丐箩。
  
具體代碼如下:

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
                         Bundle savedInstanceState) {
    View rootView = inflater.inflate(R.layout.myfrag, container, false);

    detector = new GestureDetectorCompat(getActivity(), new RecyclerViewOnGestureListener());

    recyclerView = (RecyclerView) rootView.findViewById(R.id.recyclerview);

    layoutManager = new LinearLayoutManager(getActivity());
    recyclerView.setLayoutManager(layoutManager);
    recyclerView.addOnItemTouchListener(this);

    adapter = new MyAdapter(myData));
    recyclerView.setAdapter(adapter);
    return rootView;
}

private class RecyclerViewOnGestureListener extends SimpleOnGestureListener {

    @Override
    public boolean onSingleTapConfirmed(MotionEvent e) {
        View view = recyclerView.findChildViewUnder(e.getX(), e.getY());
        int position = recyclerView.getChildPosition(view);

        // handle single tap

        return super.onSingleTapConfirmed(e);
    }

    public void onLongPress(MotionEvent e) {
        View view = recyclerView.findChildViewUnder(e.getX(), e.getY());
        int position = recyclerView.getChildPosition(view);

        // handle long press

        super.onLongPress(e);
    }
}

@Override
public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
    detector.onTouchEvent(e);
    return false;
}

@Override
public void onTouchEvent(RecyclerView rv, MotionEvent e) {
}

好了摇邦,上面分析了如何實現(xiàn)類似IOS的分組懸停效果,對解決這個問題的思路進行了闡述屎勘,這里大致總結(jié)下:


Android分組懸停 + 事件處理思路總結(jié)

參考:

1施籍、深入理解ItemDecoration

2、靈感來源

3概漱、recyclerView的事件處理

4丑慎、手勢檢測

?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市瓤摧,隨后出現(xiàn)的幾起案子竿裂,更是在濱河造成了極大的恐慌,老刑警劉巖照弥,帶你破解...
    沈念sama閱讀 218,284評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件腻异,死亡現(xiàn)場離奇詭異,居然都是意外死亡这揣,警方通過查閱死者的電腦和手機悔常,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,115評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來给赞,“玉大人这嚣,你說我怎么就攤上這事∪悖” “怎么了姐帚?”我有些...
    開封第一講書人閱讀 164,614評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長障涯。 經(jīng)常有香客問我罐旗,道長,這世上最難降的妖魔是什么唯蝶? 我笑而不...
    開封第一講書人閱讀 58,671評論 1 293
  • 正文 為了忘掉前任九秀,我火速辦了婚禮,結(jié)果婚禮上粘我,老公的妹妹穿的比我還像新娘鼓蜒。我一直安慰自己,他們只是感情好征字,可當我...
    茶點故事閱讀 67,699評論 6 392
  • 文/花漫 我一把揭開白布都弹。 她就那樣靜靜地躺著,像睡著了一般匙姜。 火紅的嫁衣襯著肌膚如雪畅厢。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,562評論 1 305
  • 那天氮昧,我揣著相機與錄音框杜,去河邊找鬼浦楣。 笑死,一個胖子當著我的面吹牛咪辱,可吹牛的內(nèi)容都是我干的振劳。 我是一名探鬼主播,決...
    沈念sama閱讀 40,309評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼油狂,長吁一口氣:“原來是場噩夢啊……” “哼澎迎!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起选调,我...
    開封第一講書人閱讀 39,223評論 0 276
  • 序言:老撾萬榮一對情侶失蹤夹供,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后仁堪,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體哮洽,經(jīng)...
    沈念sama閱讀 45,668評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,859評論 3 336
  • 正文 我和宋清朗相戀三年弦聂,在試婚紗的時候發(fā)現(xiàn)自己被綠了鸟辅。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,981評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡莺葫,死狀恐怖匪凉,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情捺檬,我是刑警寧澤再层,帶...
    沈念sama閱讀 35,705評論 5 347
  • 正文 年R本政府宣布,位于F島的核電站堡纬,受9級特大地震影響聂受,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜烤镐,卻給世界環(huán)境...
    茶點故事閱讀 41,310評論 3 330
  • 文/蒙蒙 一蛋济、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧炮叶,春花似錦碗旅、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,904評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至积瞒,卻和暖如春川尖,著一層夾襖步出監(jiān)牢的瞬間登下,已是汗流浹背茫孔。 一陣腳步聲響...
    開封第一講書人閱讀 33,023評論 1 270
  • 我被黑心中介騙來泰國打工叮喳, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人缰贝。 一個月前我還...
    沈念sama閱讀 48,146評論 3 370
  • 正文 我出身青樓馍悟,卻偏偏與公主長得像,于是被迫代替她去往敵國和親剩晴。 傳聞我的和親對象是個殘疾皇子锣咒,可洞房花燭夜當晚...
    茶點故事閱讀 44,933評論 2 355

推薦閱讀更多精彩內(nèi)容