仿網(wǎng)易新聞添加欄目和拖拽欄目效果(一)

今天偶然在apkbus上看到了以下欄目拖拽功能,我們也化繁為簡(jiǎn),一步一步來(lái)簡(jiǎn)單實(shí)現(xiàn)。
第一步,實(shí)現(xiàn)拖拽;
第二步,拖拽后的動(dòng)畫;
通過這篇文章可以了解的內(nèi)容:
第一,view的拖拽;
第二,屬性動(dòng)畫的執(zhí)行;
第三,view的位置計(jì)算(各種相對(duì)位置,比如相對(duì)屏幕,相對(duì)父view等等)
第四,拖拽影像的產(chǎn)生。

introduce.gif

第一步實(shí)現(xiàn)拖拽

通過對(duì)其源碼的分析,實(shí)現(xiàn)拖拽的主要用法就是如下代碼:

private static final ClipData EMPTY_CLIP_DATA = ClipData.newPlainText("", "");
@Override
public boolean onLongClick(View v) {
    mTvDrag.startDrag(EMPTY_CLIP_DATA,new View.DragShadowBuilder(),mTvDrag,0);
    return false;
}

注意這里只是設(shè)置在某個(gè)事件后(比如這里長(zhǎng)按事件)開始拖拽,這以后,如果我們按著view進(jìn)行拖拽的話,那么拖拽監(jiān)聽將可以監(jiān)控到,雖然此時(shí)view不能拖動(dòng)。
那我們?nèi)绾卧O(shè)置監(jiān)聽呢?上代碼:

mTvDrag.setOnDragListener(this);

設(shè)置監(jiān)聽還有另外一種方式,就是調(diào)用了startDrag方法的view的父類中去實(shí)現(xiàn)onDragEvent()方法,這樣也可以監(jiān)聽到view的拖拽。
注意點(diǎn):
誰(shuí)負(fù)責(zé)監(jiān)聽,那么這個(gè)監(jiān)聽的view就是可拖拽的范圍,這個(gè)很重要,因?yàn)檫@涉及到拖拽時(shí)坐標(biāo)的計(jì)算

@Override
public boolean onDragEvent(DragEvent event) {
    int action = event.getAction();
    // 拖拽點(diǎn)x和y坐標(biāo)
    int eventX = (int) event.getX();
    int eventY = (int) event.getY();

    switch (action) {
        // 拖拽開始監(jiān)聽
        case DragEvent.ACTION_DRAG_STARTED:
            break;
        // 拖拽進(jìn)入時(shí)監(jiān)聽,可以開始進(jìn)行拖拽
        // 和STARTED區(qū)別稍后講
        case DragEvent.ACTION_DRAG_ENTERED:
            Log.d("zp_test", "ENTERED " + event.getY());
            break;
        // 拖拽進(jìn)行中
        case DragEvent.ACTION_DRAG_LOCATION:
            break;
        // 拖拽結(jié)束
        case DragEvent.ACTION_DRAG_ENDED:
        case DragEvent.ACTION_DRAG_EXITED:
            break;
        // 拖拽松開
        case DragEvent.ACTION_DROP:
            break;
    }
    return true;
}

也許你會(huì)講,直接用onTouchListener和layout方法也可以實(shí)現(xiàn)view跟隨手指一起滑動(dòng)啊!沒錯(cuò)是可以,但是用它來(lái)實(shí)現(xiàn)更為復(fù)雜的內(nèi)容時(shí),那將是場(chǎng)災(zāi)難,比如長(zhǎng)按后拖動(dòng),點(diǎn)擊事件不被屏蔽等等,實(shí)現(xiàn)起來(lái)就顯得稍微麻煩了。

第二步繪制影像陰影

細(xì)心的你一定能夠發(fā)現(xiàn),在拖拽的時(shí)候,拖拽的view是透明度比本身的view要低的一個(gè)影像。拖拽實(shí)現(xiàn)的原理其實(shí)是:在可拖拽區(qū)域放置一個(gè)framelayout,在framelayout中有一個(gè)隱藏的imageview,當(dāng)你長(zhǎng)按哪個(gè)可拖動(dòng)的view的時(shí)候,獲取到這個(gè)view的位置,然后通過復(fù)制這個(gè)view生成一個(gè)bitmap對(duì)象復(fù)制給framelayout中隱藏的imageview。然后根據(jù)手指位置來(lái)設(shè)置隱藏的imageview(此時(shí)顯示這個(gè)imageview)位置。
由此可見,所謂的拖拽其實(shí)是在拖拽一個(gè)影像,影像是最先設(shè)置在framelayout中的一個(gè)imageview。通過不斷更新imageview的setX和setY來(lái)進(jìn)行拖動(dòng)。

通過一個(gè)view來(lái)獲取其cache背景圖,從而生成一個(gè)跟其一模一樣的bitmap對(duì)象。

private Bitmap createDraggedChildBitmap(View view) {
    view.setDrawingCacheEnabled(true);
    final Bitmap cache = view.getDrawingCache();

    Bitmap bitmap = null;
    if (cache != null) {
        try {
            bitmap = cache.copy(Bitmap.Config.ARGB_8888, false);
        } catch (final OutOfMemoryError e) {
            Log.w("zp_test", "Failed to copy bitmap from Drawing cache", e);
            bitmap = null;
        }
    }

    view.destroyDrawingCache();
    view.setDrawingCacheEnabled(false);

    return bitmap;
}

至于計(jì)算當(dāng)前view的位置,那么就需要你先了解以下這些內(nèi)容:

第一點(diǎn)
getTop(),getLeft(),getRight(),getBottom()
這四個(gè)方法是指相對(duì)于父view的位置,并且一般情況下它的值是不會(huì)發(fā)生變化的,除非有屬性動(dòng)畫改變其位置,或者重新進(jìn)行l(wèi)ayout方法的調(diào)用。

第二點(diǎn)
在拖拽監(jiān)聽中int eventX = (int) event.getX();int eventY = (int) event.getY();
不同拖拽事件對(duì)應(yīng)的eventx和eventy是不同的,具體如下:STARTED事件下對(duì)應(yīng)是拖拽點(diǎn)與屏幕邊界的距離(包括狀態(tài)欄),ENTERED對(duì)應(yīng)是拖拽點(diǎn)與界面邊界的距離(不包括狀態(tài)欄)、LOCATION、DROP事件下對(duì)應(yīng)的是拖拽點(diǎn)和父view邊界的距離,END時(shí)為0。

第三點(diǎn)
當(dāng)進(jìn)行拖拽時(shí):
依次執(zhí)行started,entered,若干個(gè)location,然后drop,end這樣一個(gè)執(zhí)行順序。

如下圖:

第二點(diǎn)示意圖1.jpg

接下來(lái)我們開工:
我們長(zhǎng)按一個(gè)view,然后在同樣的位置生成一個(gè)影像。這個(gè)工作適合在started事件中去完成。

case DragEvent.ACTION_DRAG_STARTED:
    Log.d("zp_test", "STARTED " + event.getY() + " x: " + event.getX());
    if (mDrayListener != null) {
        // 7.0以下eventX,eventY是相對(duì)于屏幕邊界線的
        View drag = getViewFromPositionRelativeScreen(eventX, eventY);
        if (drag == null) {
            Log.e("zp_test", "drag is null");
            break;
        }
        int[] location = new int[2];
        drag.getLocationInWindow(location);
        mPointToBorderX = eventX - location[0];
        mPointToBorderY = eventY - location[1];

        mDrayListener.onDragStart(createDraggedChildBitmap(drag), drag);
    }
    break;
private int[] location = new int[2];
private View getViewFromPositionRelativeScreen(int x, int y) {
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
        getLocationInWindow(location);
        return getViewFromPositionRelativeFather(x - location[0], y - location[1]);
    } else {
        return getViewFromPositionRelativeFather(x, y);
    }
}
private View getViewFromPositionRelativeFather(int x, int y) {
    Log.d("zp_test", "x: " + x + " y: " + y);
    int childCount = getChildCount();
    Log.d("zp_test", "childCount: " + childCount);
    if (childCount <= 0)
        return null;

    for (int i = 0; i < childCount; i++) {
        View view = getChildAt(i);
        Log.d("zp_test", "view l : " + view.getX()
                + " view r: " + view.getX() + view.getWidth()
                + " view t: " + view.getY()
                + " view b: " + view.getY() + view.getHeight());
        // 根據(jù)點(diǎn)擊位置獲取view
        if (y >= view.getY() && y <= view.getY() + view.getHeight()
                && x >= view.getX() && x <= view.getX() + view.getWidth())
            return view;
    }

    return null;
}

上面兩個(gè)方法就可以從點(diǎn)擊點(diǎn)獲取到點(diǎn)擊在哪個(gè)view上。
注意以下兩句代碼很重要
mPointToBorderX = eventX - location[0]; mPointToBorderY = eventY - location[1];
這個(gè)是在求得點(diǎn)擊點(diǎn)和被拖拽的view邊界的距離,因?yàn)槲覀円L制影像的位置,就必須知道被拖拽的view到其父view的位置,這個(gè)就是影像的位置,但是我們從監(jiān)聽方法中只能知道點(diǎn)擊點(diǎn)的坐標(biāo)位置,我們還得求得這個(gè)view邊界到父view的位置,也就是得減去點(diǎn)擊點(diǎn)到拖拽view邊界的距離
影像y坐標(biāo) = (點(diǎn)擊點(diǎn)y坐標(biāo)) - (點(diǎn)擊點(diǎn)到view邊界的距離)

示意圖2.jpg

最開始我沒有做這個(gè)計(jì)算,所以導(dǎo)致每次從STARTED事件到ENTERED事件時(shí),都會(huì)跳動(dòng)一下,而這個(gè)跳動(dòng)的距離實(shí)際上就是mPointToBorderX 和mPointToBorderY 。

拖拽示意圖.gif

到這里,我們就實(shí)現(xiàn)了拖拽功能了,而后的功能在下篇進(jìn)行介紹,最后,因?yàn)橹挥袔讉€(gè)類就不分享到github了,給出百度鏈接:
拖拽功能code.zip

如有錯(cuò)誤,歡迎指出。(本人最近已離職,在上海如有工作推薦,麻煩各位留言或者私信我,謝謝大家!)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,362評(píng)論 6 544
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,577評(píng)論 3 429
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,486評(píng)論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,852評(píng)論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,600評(píng)論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,944評(píng)論 1 328
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,944評(píng)論 3 447
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 43,108評(píng)論 0 290
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,652評(píng)論 1 336
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,385評(píng)論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,616評(píng)論 1 374
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,111評(píng)論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,798評(píng)論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,205評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,537評(píng)論 1 295
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,334評(píng)論 3 400
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,570評(píng)論 2 379

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

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,748評(píng)論 25 708
  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫(kù)、插件、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 12,176評(píng)論 4 61
  • 激蕩三十年讀后感 第一次聽說激蕩三十年這本書是在大三上學(xué)期管理學(xué)老師那聽說的,老師布置了一個(gè)任務(wù),把這兩冊(cè)書讀完,...
    解小太陽(yáng)閱讀 1,622評(píng)論 0 5
  • 神也無(wú)法壓制我的勃勃雄心壯志 對(duì)啊對(duì)啊又開始寫plan了乛乛 雖然每次放假都會(huì)寫然后開學(xué)時(shí)總是發(fā)現(xiàn)[em]e400...
    埋下胡楊閱讀 317評(píng)論 0 0
  • 每個(gè)人最后的依靠都只有自己. 別盲信任何人. 你和他,不會(huì)走到最后.因?yàn)椋憧床煌杆闶峭活惾耍活惾?..
    Sabrina667閱讀 164評(píng)論 0 0