首頁  >  問答  >  主體

关于android如何实现view组件的drag功能的疑问

我尝试实现一个在屏幕上拖动view, 让view移动的功能.

我是这样做得:

但是出现了下面很奇怪的问题: (见录制的屏幕)

似乎按钮只会在某个小窗口内部分是visible的, 其他的话就被盖住.

可是自己设置了framelayout的大小都是match_parent, 可能framelayout给每个fragment分配的空间太小了?
可是我尝试改变了view的maxwidth也不行.....

自己不太知道该如何解决, 希望大家帮忙! 多谢!

PHP中文网PHP中文网2713 天前787

全部回覆(1)我來回復

  • 怪我咯

    怪我咯2017-04-17 13:08:26

    監聽TouchEvent跟你想的差不多..我通常這樣操作.

    1.Down事件發生後,獲得需要移動的View的cache bitmap

    Bitmap bitmap = targetView.getDrawingCache()
    或者调用targetView的draw方法绘制在自己创建的canvas上来得到当前快照
    

    2.新建一個ImageView,把快照丟進去
    3.使用WindowMananger在螢幕上加上一個圖層,把ImageView丟到你的按鈕原來的位置上,把你原來那個按鈕隱藏
    4.然後剩下的和你的思路一致,你只要移動WindowManager裡的ImageView就好了...這個圖層是全屏...不受限制
    5.ACTION_UP以後...把WindowMananger的內容刪除,把你的Button移到Action_up發生的位置.

    不推薦的你 做法是因為 直接修改View的位置...會引起整個View tree的重佈局...非常影響性能..
    而且你要改的不是setx sety....是LayoutParamters裡的xy
    View的setx sety是繪圖的起始位置...相當於setTranslationX/Y view根本沒動...


    補充:
    1.關於WindowMananger方案請參考這段程式碼:

     wm = (WindowManager) getApplicationContext().getSystemService("window");  
    
        wmParams = new WindowManager.LayoutParams();  
        wmParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_ERROR;  
        wmParams.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;  
        wmParams.gravity = Gravity.LEFT | Gravity.TOP;  
    
        wmParams.x = 0;  
        wmParams.y = 0;  
    
        wmParams.width = WindowManager.LayoutParams.WRAP_CONTENT;  
        wmParams.height = WindowManager.LayoutParams.WRAP_CONTENT;  
        wmParams.format = PixelFormat.RGBA_8888;  
    
        wm.addView(view, wmParams);  
    
        view.setOnTouchListener(new OnTouchListener() {  
    
            public boolean onTouch(View v, MotionEvent event) {  
    
                x = event.getRawX();  
                y = event.getRawY();  
    
                switch (event.getAction()) {  
    
                case MotionEvent.ACTION_DOWN:  
                    mTouchStartX = event.getX();  
                    mTouchStartY = event.getY() + view.getHeight() / 2;  
                    break;  
    
                case MotionEvent.ACTION_MOVE:  
                    updateViewPosition();  
                    break;  
    
                case MotionEvent.ACTION_UP:  
                    updateViewPosition();  
                    mTouchStartX = mTouchStartY = 0;  
                    break;  
                }  
    
                return true;  
            }  
        });  
    

    --

         private void updateViewPosition() {  
    
        wmParams.x = (int) (x - mTouchStartX);  
        wmParams.y = (int) (y - mTouchStartY);  
        wm.updateViewLayout(view, wmParams);  
    }  
    

    2.ViewGroup其實有個ClipChildren的私有欄位控制當childview超出子佈局的時候是否要繪製.
    應用的範例: https://github.com/dkmeteor/CircleList

    核心程式碼:

    public void changeGroupFlag(Object obj) throws Exception
    {
        Field[] f = obj.getClass().getSuperclass().getSuperclass().getSuperclass().getDeclaredFields(); // 获得成员映射数组
        for (Field tem : f)
        {
        if (tem.getName().equals("mGroupFlags")) {
        tem.setAccessible(true);
        Integer mGroupFlags = (Integer)tem.get(obj);
        int newGroupFlags = mGroupFlags & 0xfffff8;
        tem.set(obj, newGroupFlags);
        }
        }
    }
    

    注意這個屬性是私有欄位,該Demo中使用反射修改了mGroupFlags欄位...不建议使用.

    3.如何在setLayoutParams裡面像setX, setY一樣設定位置
    那取決於LayoutParams的種類,LayoutParams又取決於該View的父佈局
    LayoutParams有很多很多種
    例如 ViewGroup.LayoutParams RelativeLayout.LayoutParams WindowManager.LayoutParams 等很多種,每一種支援的屬性不一樣.

    例如AbsoluteLayout.LayoutParams 就有 x y屬性可以直接設定位置
    但是 LayoutParams.LayoutParams 就只能設定margin

    4.你對View繪製流程的理解有偏差.
    這部分有點複雜......View的繪製是由自身完成的...View不會繪製超出自己bounds/rect區域以外的部分....我講不大清楚...
    你可以參考Android 5.0 View原始碼 14959行~14977行(在View.draw 中間那一段) 關於clipRect部分的邏輯

    我在上面回答2裡 CircleList 裡使用方法 實際上最終就是透過反射修改了 ViewGroup.mGroupFlags的值,該值在繪圖流程中會影響子View繪製時Clip這部分的邏輯,

    PS.試著寫了一下..發現我是說不清楚這個問題了....

    回覆
    0
  • 取消回覆