FreeSizeDraggableLayout实现过程解析

简介

FreeSizeDraggableLayout是一个ViewGroup,将内部空间分为了m*n大小的单元格,其中的子控件可自定义大小及位置,拥有相同大小见的控件可通过拖拽完成位置交换。
在V0.0.1中,只有大小相同的两个子控件可进行位置交换
在V0.0.2中,与被拖拽控件大小相同的控件组也可完成与被拖拽控件的交换,能够通过setGroupChangeEnable(false)禁止该功能。
FreeSizeDraggableLayout主页
Demo下载
使用效果:
FreeSizeDraggableLayout

使用方法

1.Gradle中导入包

dependencies {
   compile 'com.miao:freesizedraggablelayout:0.0.2'
}

2.在XML中定义该控件

    <com.miao.freesizedraggablelayout.FreeSizeDraggableLayout
        android:id="@+id/fsd_layout"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

3.设置控件中单元格的个数

FreeSizeDraggableLayout fsdLayout = (FreeSizeDraggableLayout) findViewById(R.id.fsd_layout);
fsdLayout.setUnitWidthNum(4);
fsdLayout.setUnitHeightNum(4);

4.创建一个DetailView

List<DetailView> list = new ArrayList<>();
/*
DetailView:
public DetailView(Point p, int width, int height, View v) {
    setPoint(p);
    setWidhtNum(width);
    setHeightNum(height);
    setView(v);
}
*/
list.add(new DetailView(new Point(0, 0), 2, 2, createButton(1)));
list.add(new DetailView(new Point(2, 0), 2, 2, createButton(2)));
list.add(new DetailView(new Point(0, 2), 2, 2, createButton(3)));
list.add(new DetailView(new Point(2, 2), 2, 2, createButton(4)));
/*
private Button createButton(int i) {
    final Button btn = new Button(getApplicationContext());
    btn.setText("Button - " + i);
    btn.setBackgroundColor(Color.BLACK);
    btn.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            Toast.makeText(getApplicationContext(), btn.getText(), Toast.LENGTH_SHORT).show();
        }
    });
    return btn;
}
*/

5.将该list设为FreeSizeDraggagbleLayout的数据源

freeSizeDraggableLayout.setList(list);

实现原理

1. 长按控件产生控件浮起效果实现

用户点击控件后,程序首先通过getClickedItem函数获取被点击的控件,确定用户点击的是有效控件时,设置一个时常为mResponseTime的定时器,判断用户确实为长按控件后,调用createPressImageView函数,根据该控件的样式绘制一个半透明的ImageView,通过WindowManager的addView加至FreeSizeDraggableLayout中,同时隐藏被点击的控件(viewPress.setVisibility(View.INVISIBLE)),从而实现了长按控件产生控件浮起的效果

2. 被拖动控件与单个相同大小控件换位效果实现

实现过程在DispadispatchTouchEvent的ACTION_MOVE过程中,
1.首先判断程序是处在拖拽控件的状态,若不在则不执行动作
2.若处于拖动状态,实时更新ImageView(被拖拽控件的图像)的位置,跟随手指移动。
3.判断拖动位置是否处于有效控件位置,并判断该位置的控件是否与拖拽控件大小相同,若相同则交换位置。
交换位置通过更新listView中对应子控件的Point参数,并调用函数DrawViewsAtList完成。

3. 拖动控件及与控件组(由多个控件组成,组成后与被拖动控件具有相同大小)换位效果实现

这个实现有点麻烦,大致写一下实现思路:

  1. 以被拖拽控件覆盖的子控件的左上角为起点,计算长宽与被拖拽控件相等的矩形
  2. 遍历listView,统计所有在该矩形内的控件
  3. 计算这些符合条件的控件的总面积,若与被拖拽控件相等,表明这些子控件组可组成大小与被拖拽控件相等的控件组,交换他们的位置。

这里采用遍历所有符合条件的控件,计算总面积的方式作为是否大小相等的判定标准,暂时还没想到更好的判断方法
核心代码:
判断是否存在可交换控件组的函数:ChangeableViewGroupExist
交换位置的方法参考函数:changeGroupPos

小结

  1. 本ViewGroup主要通过listView保存子控件的信息。在控件交换位置后,需更新listView中DetailView的位置信息,即Point,并重绘对应的view。
  2. 最开始采用的交换位置实现方式是在修改listView中各DetailView的point信息后调用removeAllChildView&AddView,但该种方式虽然实现简单,但在换位过程中需要重复的添加和移除view,效果比较糟糕。采用重绘位置的方式效率会高很多。