前言
- ViewDragHelper 是一個(gè)用于編寫自定義 ViewGroup 的工具類伺帘,它提供了許多有用的操作和狀態(tài)跟蹤,允許用戶在其父 ViewGroup 中拖動(dòng)和重新定位視圖损离。
- 本文是結(jié)合 ViewDragHelper 和自定義 ViewGroup 相關(guān)知識(shí)來實(shí)現(xiàn)固定頭部與列表的拖拽功能厉熟。
1.概述
1.1 功能描述
在自定義的 ViewGroup 中放兩個(gè)布局叉谜,本例中頭部是一個(gè)固定的 TextView 错敢,也可以是其他任意布局,而拖動(dòng)部分為了簡單起見厂画,是一個(gè) ListView凸丸,也可以是 Recyclerview。當(dāng)然袱院,無論是固定還是拖動(dòng)部分屎慢,都是可以修改成其他的布局,此處主要講述的是 ViewGroup 中如何結(jié)合 ViewDragHelper 實(shí)現(xiàn)一些效果忽洛。下圖是實(shí)現(xiàn)的效果圖腻惠。
1.2 涉及主要知識(shí)點(diǎn)
- 自定義 ViewGroup
- ViewDragHelper 的使用
- 事件分發(fā)機(jī)制
1.3 實(shí)現(xiàn)步驟分析
- 新建 VerticalDragListView extends FrameLayout,做好初始化工作
- 構(gòu)建 ViewDragHelper 實(shí)例
- 指定可拖拽的 View
- 計(jì)算相關(guān)拖拽點(diǎn)
- 計(jì)算相關(guān)滑動(dòng)距離
- 滑動(dòng)攔截
2.具體實(shí)現(xiàn)
此處我直接上代碼欲虚,關(guān)聯(lián)性邏輯較多集灌,相關(guān)內(nèi)容看注釋。
public class VerticalDragListView extends FrameLayout {
// 可以認(rèn)為這是系統(tǒng)給我們寫好的一個(gè)工具類
private ViewDragHelper mDragHelper;
private View mDragListView;
// 后面菜單的高度
private int mMenuHeight;
// 菜單是否打開
private boolean mMenuIsOpen = false;
public VerticalDragListView(Context context) {
this(context, null);
}
public VerticalDragListView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public VerticalDragListView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mDragHelper = ViewDragHelper.create(this, mDragHelperCallback);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
if (changed) {
View menuView = getChildAt(0);
mMenuHeight = menuView.getMeasuredHeight();
}
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
int childCount = getChildCount();
if (childCount != 2) {
throw new RuntimeException("VerticalDragListView 只能包含兩個(gè)子布局");
}
mDragListView = getChildAt(1);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
mDragHelper.processTouchEvent(event);
return true;
}
// 1.拖動(dòng)我們的子View
private ViewDragHelper.Callback mDragHelperCallback = new ViewDragHelper.Callback() {
@Override
public boolean tryCaptureView(View child, int pointerId) {
// 指定該子View是否可以拖動(dòng),就是 child
// 只能是列表可以拖動(dòng)
// 2.1 固定頭部不能拖動(dòng)
return mDragListView == child;
}
@Override
public int clampViewPositionVertical(View child, int top, int dy) {
// 垂直拖動(dòng)移動(dòng)的位置
// 2.3 垂直拖動(dòng)的范圍只能是頭部 View 的高度
if (top <= 0) {
top = 0;
}
if (top >= mMenuHeight) {
top = mMenuHeight;
}
return top;
}
// 2.2 列表只能垂直拖動(dòng)
/*@Override
public int clampViewPositionHorizontal(View child, int left, int dx) {
// 水平拖動(dòng)移動(dòng)的位置
return left;
}*/
// 2.4 手指松開的時(shí)候兩者選其一欣喧,要么打開要么關(guān)閉
@Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {
if (releasedChild == mDragListView) {
if (mDragListView.getTop() > mMenuHeight / 2) {
// 滾動(dòng)到頭部的高度(打開)
mDragHelper.settleCapturedViewAt(0, mMenuHeight);
mMenuIsOpen = true;
} else {
// 滾動(dòng)到0的位置(關(guān)閉)
mDragHelper.settleCapturedViewAt(0, 0);
mMenuIsOpen = false;
}
invalidate();
}
}
};
private float mDownY;
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
// 頭部打開要攔截
if (mMenuIsOpen) {
return true;
}
// 向下滑動(dòng)攔截腌零,不要給ListView做處理
// 誰攔截誰 父View攔截子View ,但是子 View 可以調(diào)這個(gè)方法
// requestDisallowInterceptTouchEvent 請(qǐng)求父View不要攔截唆阿,改變的其實(shí)就是 mGroupFlags 的值
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
mDownY = ev.getY();
// 讓 DragHelper 拿一個(gè)完整的事件
mDragHelper.processTouchEvent(ev);
break;
case MotionEvent.ACTION_MOVE:
float moveY = ev.getY();
if ((moveY - mDownY) > 0 && !canChildScrollUp()) {
// 向下滑動(dòng) && 滾動(dòng)到了頂部益涧,攔截不讓ListView做處理
return true;
}
break;
}
return super.onInterceptTouchEvent(ev);
}
/**
* @return Whether it is possible for the child view of this layout to
* scroll up. Override this if the child view is a custom view.
* 此處參考系統(tǒng) SwipeRefreshLayout 中的方法,判斷View是否滾動(dòng)到了最頂部,還能不能向上滾
*/
public boolean canChildScrollUp() {
if (android.os.Build.VERSION.SDK_INT < 14) {
if (mDragListView instanceof AbsListView) {
final AbsListView absListView = (AbsListView) mDragListView;
return absListView.getChildCount() > 0
&& (absListView.getFirstVisiblePosition() > 0 || absListView.getChildAt(0)
.getTop() < absListView.getPaddingTop());
} else {
return ViewCompat.canScrollVertically(mDragListView, -1) || mDragListView.getScrollY() > 0;
}
} else {
return ViewCompat.canScrollVertically(mDragListView, -1);
}
}
/**
* 響應(yīng)滾動(dòng)
*/
@Override
public void computeScroll() {
if (mDragHelper.continueSettling(true)) {
invalidate();
}
}
}
在Activity中只作簡單的測(cè)試實(shí)現(xiàn)
public class MainActivity extends AppCompatActivity {
private ListView mListView;
private List<String> mItems;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mListView = (ListView) findViewById(R.id.list_view);
mItems = new ArrayList<String>();
for (int i = 0; i < 100; i++) {
mItems.add("i -> " + i);
}
mListView.setAdapter(new BaseAdapter() {
@Override
public int getCount() {
return mItems.size();
}
@Override
public Object getItem(int position) {
return null;
}
@Override
public long getItemId(int position) {
return 0;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
TextView item = (TextView) LayoutInflater.from(MainActivity.this)
.inflate(R.layout.item_lv, parent, false);
item.setText(mItems.get(position));
return item;
}
});
}
}
3.總結(jié)
通過上面自定義 ViewGroup 的實(shí)現(xiàn)驯鳖,可以了解到 ViewDragHelper 的神奇用法闲询,而 ViewDragHelper 的用法不僅僅于此,對(duì)于 View 的拖拽浅辙,可以實(shí)現(xiàn)更多神奇的效果扭弧,在網(wǎng)上這方面的 demo 比較多,讀者可自行搜索记舆,后續(xù)有必要再出一些炫酷效果的實(shí)現(xiàn)寄狼。