关于android如何实现view组件的drag功能的疑问
PHP中文网
PHP中文网 2017-04-17 13:06:26
0
1
869

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

我是这样做得:

  • 一个add按钮将一个fragment插入framelayout, framelayout在relativelayout中
  • fragment中包含view(在下面的例子是一个按钮)
  • view监听onTouchEvent, 当Action Down + Action Move, 就开始记录拖动, 之后setX, setY

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

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

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

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

PHP中文网
PHP中文网

认证0级讲师

全部回覆(1)
刘奇

監聽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.試著寫了一下..發現我是說不清楚這個問題了....

熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板