正文
夸克浏览器是我非常喜欢的一款浏览器,使用起来简洁流畅,UI做的也很精致。今天我就来仿写主页底部的工具栏。先来看看原本的效果:
效果比较简单,从外表看就是一个弹框,特别之处就是可以收缩伸展布局,再来看看我实现的效果:
怎么样?效果是不是已经非常接近。先整体说下思路吧,底部对话框用
DialogFragment
来实现,里面的可伸缩布局采用自定义
ViewGroup
。看了本文你将能学到(巩固)以下知识点:
-
DialogFragment
的用法;
-
自定义
ViewGroup
的用法,包括
onMeasure
和
onLayout
方法;
-
ViewDragHelper
的用法,包括处理手势和事件冲突
听起来内容挺多的,但只要一步步去解析,其实实现过程也不算复杂。
底部对话框
底部对话框我采用了
DialogFragment
,因为相比传统的AlertDialog实现起来更简单,用法也几乎和普通的Fragment没有什么区别。
主要工作就是指定显示位置:
public class BottomDialogFragment extends DialogFragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_bottom, null);
}
public void onStart() {
super.onStart();
Dialog dialog = getDialog();
if (dialog != null && dialog.getWindow() != null) {
Window window = dialog.getWindow();
dialog.getWindow().setGravity(Gravity.BOTTOM);
dialog.getWindow().setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
window.setWindowAnimations(R.style.animate_dialog);
window.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
setCancelable(true);
}
}
}
点击显示弹框:
FragmentManager fm = getSupportFragmentManager();
BottomDialogFragment bottomDialogFragment = new BottomDialogFragment();
bottomDialogFragment.show(fm, "fragment_bottom_dialog");
自定义折叠布局
这里主要用到的就是自定义
ViewGroup
的知识了。先大致梳理一下:我们需要包含两个子view,在上面的
topView
,在下面的
bottomView
。
topView
往下滑的时候要覆盖
bottomView
。但是
ViewGroup
的显示的层次顺序和添加顺序是反过来的,后面添加的view如果和前面添加的View有重叠的话会覆盖前面会覆盖添加的view,而我们预想的布局文件应该是这样的:
<ViewGroup>
<topView/>
<bottom/>
</ViewGroup>
所以我们需要在代码中手动对换两者顺序:
@Override
protected void onFinishInflate() {
super.onFinishInflate();
if (getChildCount() != 2) {
throw new RuntimeException("必须是2个子View!");
}
topView = getChildAt(0);
bottomView = getChildAt(1);
bringChildToFront(topView);
}
这样之后
getChildAt(0)
取到的就是
bottomView
了。接下来是
onMeasure()
,计算自身的大小:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int sizeWidth = MeasureSpec.getSize(widthMeasureSpec);
int sizeHeight = MeasureSpec.getSize(heightMeasureSpec);
measureChildren(widthMeasureSpec, heightMeasureSpec);
int width = 0;
int height = 0;
for (int i = 0; i < getChildCount(); i++) {
View childView = getChildAt(i);
MarginLayoutParams cParams = (MarginLayoutParams) childView.getLayoutParams();
int cWidthWithMargin = childView.getMeasuredWidth() + cParams.leftMargin + cParams.rightMargin;
int cHeightWithMargin = childView.getMeasuredHeight() + cParams.topMargin + cParams.bottomMargin;
height = height + cHeightWithMargin;
width = cWidthWithMargin > width ? cWidthWithMargin : width;
}
setMeasuredDimension((widthMode == MeasureSpec.EXACTLY) ? sizeWidth
: width, (heightMode == MeasureSpec.EXACTLY) ? sizeHeight
: height);
}
然后自定义
onLayout()
,放置两个子View的位置:
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
for (int i = 0; i < getChildCount(); i++) {
View childView = getChildAt(i);
int cWidth = childView.getMeasuredWidth();
int cHeight = childView.getMeasuredHeight();
MarginLayoutParams cParams = (MarginLayoutParams) childView.getLayoutParams();
int cl = 0, ct = 0, cr = 0, cb = 0;
switch (i) {
case 0:
cl = cParams.leftMargin;
ct = getHeight() - cHeight - cParams.bottomMargin;
cb = cHeight + ct ;
childView.setPadding(0, extendHeight, 0, 0);
cr = cl + cWidth;
break;
case 1:
cl = cParams.leftMargin;
ct = cParams.topMargin;
cb = cHeight + ct;
cr = cl + cWidth;
break;
}
childView.layout(cl, ct, cr, cb);
}
}
这样之后,就可以显示布局了,但还是不能滑动。处理滑动我采用
了ViewDragHelper
,这个工具类可谓自定义
ViewGroup
神器。有了它,
ViewGroup
可以很容易的控制各个子View的滑动。什么事件分发,滑动冲突都不需要我们操心了。
mDragger = ViewDragHelper.create(this, 1.0f, new ViewDragHelperCallBack())
创建实例需要3个参数,第一个就是当前的ViewGroup,第二个是
sensitivity
(敏感系数,联想下鼠标灵敏度就知道了)。第三个参数就是Callback,会在触摸过程中会回调相关方法,也是我们主要需要实现的方法。
private class ViewDragHelperCallBack extends ViewDragHelper.Callback {
@Override
public boolean tryCaptureView(View child, int pointerId) {
return topView == child;
}
@Override
public int clampViewPositionHorizontal(View child, int left, int dx) {
return 0;
}
@Override
public int getViewVerticalDragRange(View child) {
return getMeasuredHeight() - child.getMeasuredHeight();
}
@Override
public int clampViewPositionVertical(View child, int top, int dy){
final int topBound = getPaddingTop();
final int bottomBound = getHeight() - child.getHeight() - topBound;
return Math.min(Math.max(top, topBound), bottomBound);
}
@Override