夸克浏览器是我非常喜欢的一款浏览器,使用起来简洁流畅,UI做的也很精致。今天我就来仿写主页底部的工具栏。先来看看原本的效果:

效果比较简单,从外表看就是一个弹框,特别之处就是可以收缩伸展布局,再来看看我实现的效果:

怎么样?效果是不是已经非常接近。先整体说下思路吧,底部对话框用DialogFragment来实现,里面的可伸缩布局采用自定义ViewGroup。看了本文你将能学到(巩固)以下知识点:

  • DialogFragment的用法;
  • 自定义ViewGroup的用法,包括onMeasureonLayout方法;
  • ViewDragHelper的用法,包括处理手势和事件冲突

听起来内容挺多的,但只要一步步去解析,其实实现过程也不算复杂。

底部对话框

底部对话框我采用了DialogFragment,因为相比传统的AlertDialog实现起来更简单,用法也几乎和普通的Fragment没有什么区别。 主要工作就是指定显示位置:

public class BottomDialogFragment extends DialogFragment {@Overridepublic 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,在下面的bottomViewtopView往下滑的时候要覆盖bottomView。但是ViewGroup的显示的层次顺序和添加顺序是反过来的,后面添加的view如果和前面添加的View有重叠的话会覆盖前面会覆盖添加的view,而我们预想的布局文件应该是这样的:

<ViewGroup><topView/><bottom/>
</ViewGroup>
复制代码

所以我们需要在代码中手动对换两者顺序:

 @Overrideprotected void onFinishInflate() {super.onFinishInflate();if (getChildCount() != 2) {throw new RuntimeException("必须是2个子View!");}topView = getChildAt(0);bottomView = getChildAt(1);bringChildToFront(topView);}
复制代码

这样之后getChildAt(0)取到的就是bottomView了。接下来是onMeasure(),计算自身的大小:

    /*** 计算所有ChildView的宽度和高度 然后根据ChildView的计算结果,设置自己的宽和高*/@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {/*** 获得此ViewGroup上级容器为其推荐的宽和高,以及计算模式*/int widthMode = MeasureSpec.getMode(widthMeasureSpec);int heightMode = MeasureSpec.getMode(heightMeasureSpec);int sizeWidth = MeasureSpec.getSize(widthMeasureSpec);int sizeHeight = MeasureSpec.getSize(heightMeasureSpec);// 计算出所有的childView的宽和高measureChildren(widthMeasureSpec, heightMeasureSpec);int width = 0;int height = 0;/*** 根据childView计算的出的宽和高,以及设置的margin计算容器的宽和高,主要用于容器是warp_content时*/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;//高度为两个子view的和height = height + cHeightWithMargin;//宽度取两个子view中的最大值width = cWidthWithMargin > width ? cWidthWithMargin : width;}/*** 如果是wrap_content设置为我们计算的值* 否则:直接设置为父容器计算的值*/setMeasuredDimension((widthMode == MeasureSpec.EXACTLY) ? sizeWidth: width, (heightMode == MeasureSpec.EXACTLY) ? sizeHeight: height);}
复制代码

然后自定义onLayout(),放置两个子View的位置:

 @Overrideprotected void onLayout(boolean changed, int l, int t, int r, int b) {/*** 遍历所有childView根据其宽和高,以及margin进行布局*/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://bottomView放下面cl = cParams.leftMargin;ct = getHeight() - cHeight - cParams.bottomMargin;cb = cHeight + ct ;childView.setPadding(0, extendHeight, 0, 0);cr = cl + cWidth;break;case 1://topView放上面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 {@Overridepublic boolean tryCaptureView(View child, int pointerId) {return topView == child;//限制只有topView可以滑动}@Overridepublic int clampViewPositionHorizontal(View child, int left, int dx) {return 0;//横向可滑动范围,因为不可以横向滑动直接返回0就行}@Overridepublic int getViewVerticalDragRange(View child) {return getMeasuredHeight() - child.getMeasuredHeight();}@Overridepublic int clampViewPositionVertical(View child, int top, int dy){//竖向可滑动范围,top是child即将滑动到的top值,限制top的范围在topBound和bottomBound之间。final int topBound = getPaddingTop();final int bottomBound = getHeight() - child.getHeight() -  getPaddingBottom();return Math.min(Math.max(top, topBound), bottomBound);}@Overridepublic void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {float percent = (float) top / (getHeight() - changedView.getHeight());//处理topView动画if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {changedView.setElevation(percent * 10);}//处理bottomView动画bottomView.setScaleX(1 - percent * 0.03f);}@Overridepublic void onViewReleased(View releasedChild, float xvel, float yvel) {//手指释放时,滑动距离大于一半直接滚动到底部,否则返回顶部if (releasedChild == topView) {float movePercentage = (float) (releasedChild.getTop()) / (getHeight() - releasedChild.getHeight() - elevationHeight);int finalTop = (movePercentage >= .5f) ? getHeight() - releasedChild.getHeight() - elevationHeight : 0;mDragger.settleCapturedViewAt(releasedChild.getLeft(), finalTop);invalidate();}}}
复制代码

至于处理事件分发,处理滚动全都交给ViewDragHelper做就行了:

 @Overridepublic void computeScroll() {if (mDragger.continueSettling(true)) {invalidate();}}@Overridepublic boolean onInterceptTouchEvent(MotionEvent event) {return mDragger.shouldInterceptTouchEvent(event);}@Overridepublic boolean onTouchEvent(MotionEvent event) {mDragger.processTouchEvent(event);return true;}
复制代码

总结

好了实现大致分析完了,还有一些小细节的处理和自定义View常用的回调、get/set方法就不说了,大家如果有兴趣的话就直接去看源码吧。个人觉得以上实现通用性还是不足吧,现在只能实现一层折叠,折叠方向也是固定的。作为对比,我们来看下Android系统通知栏的流式折叠布局。怎么样,是不是比上面这个不知道高到哪里去了!Excited!

最近我也在琢磨如何实现(recyclerView+自定义layoutManager???)。有实现方法或源码的同学请在下方留言,感激不尽!如果我琢磨出来了也会第一时间分享出来。 最后贴下本栗的Github地址:

Github地址

12月12日更新:以上的效果我已经实现啦,请关注后续博客:

RecyclerView进阶之层叠列表(上)

仿夸克浏览器底部工具栏相关推荐

  1. Vue3 移动端浏览器底部工具栏挡住部分网页,以及ios网页放大滑动问题。

    ios网页放大问题,一般来说点击input框会放大.或者双指缩放.这个非常影响用户体验. 解决方法:在index.html中,添加meta标签. <meta name="viewpor ...

  2. 有些浏览器底部工具栏遮挡吸底内容解决方案

    去掉移动端手机浏览器头部搜索栏和底部工具栏的方法 苹果.UC浏览器.QQ浏览器: <!--删除苹果默认的工具栏和菜单栏--> <meta name="apple-mobil ...

  3. 自定义眨眼Toast(仿夸克浏览器退出Toast)

    夸克浏览器是UC推出的纯净版无广告浏览器,发现他的Toast很Nice,索性也写了一个. 样式如下: 代码如下行: /*** 吐司** @param ctx* @param content*/ pub ...

  4. 苹果手机用微信内置浏览器访问页面,如果出现微信底部工具栏遮挡页面的情况

    苹果手机用微信内置浏览器访问页面,如果出现底部工具栏遮挡页面的情况,可以用这串代码解决: pushHistory();function pushHistory() {var state1 = {tit ...

  5. html仿苹果浏览器,完美仿iPhone风格主题 领航浏览器体验

    1仿iPhone的图标式导航页 手机浏览器这个市场因其使用情况极为广泛和频繁因此吸引了无数厂商进入,不仅是传统的浏览器厂商也有许多新晋的手机软件厂商,其产品也从强调省流量.云概念.操作体验和自主核心. ...

  6. 夸克浏览器有没有linux,夸克浏览器怎么样?夸克浏览器使用说明

    这个世界上,有喜欢复杂华丽的人,也有人会因此而感到焦虑.特别是信息爆炸的网络,各种广告.无用信息冲击着你的双眼,怎么样才能专心上网?你需要一个干净的浏览器. 第 281 期豌豆荚设计奖,我们颁给一款极 ...

  7. 新版手机浏览器_夸克浏览器发布全新3.0版,AI技术创新智能化信息服务

    1月3日,以轻.快为核心,主打极简体验的夸克浏览器发布全新3.0版本,推出指令控制.搜索直达.AI引擎等多个应用AI技术创新的产品功能和交互场景,同时更新品牌LOGO,持续发力智能化信息服务升级. 夸 ...

  8. 夸克浏览器产品分析报告

    1. 文档概览 体验机型:华为荣耀V9 系统版本:EMUI5.1(Android 7.0) App版本:2.1.1.978 2. 产品简介 2.1 产品名称 夸克浏览器 2.2 产品类型 手机浏览器 ...

  9. Android仿搜狗浏览器加载动画

    Android仿搜狗浏览器加载动画 周六,国庆放假调休,今天闲来无事,就看了下搜狗浏览器的加载动画.感觉结合前面学习的基础还是能做出来的,所以就简单的实现了下,然后写下这边博客给大家参考参考,权当巩固 ...

最新文章

  1. leetcode_894. All Possible Full Binary Trees
  2. SUPPORTDIR引用的文件的加入
  3. Lintcode99 Reorder List solution 题解
  4. 搭建一台本地json服务器
  5. QT调用dll且进入DLL src code调试
  6. Asp.net中Js、Css文件压缩辅助类
  7. oracle如何取当前日期年月_Oracle获取当前年、月、日的方法
  8. 业务逻辑?到底是什么
  9. spark数据倾斜解决之提高并行度
  10. 有没有知道如何连接DB2的数据库?
  11. spark学习之sparksql语法优化
  12. Windows 7 一键恢复 - 联想拯救系统
  13. 负载均衡器ribbon和LoadBalancer
  14. Verilog语言快速入门(一)
  15. mysql limt取指定数据条数 top取指定数据条数
  16. 2020年美容师(初级)多少钱及美容师(初级)模拟考试题库
  17. Python:实现费马检测算法(附完整源码)
  18. CF #683 div.2
  19. GROUP Function
  20. 爱普生L4158使用评测及小白智慧打印离线解决办法

热门文章

  1. java 规范异常的处理_规范-异常处理
  2. 虚拟机win2003安装mysql教程_CentOS7.2虚拟机上安装MySQL 5.6.32的教程
  3. js获取浏览器高和宽的基本信息:屏幕信息
  4. matlab 变参数 方程组,解带参数方程组 运行结果竟然自己带了新参数z
  5. 修改oracle+sga+size,oracle自动内存共享管理测试。修改 oracle 11g SGA_MAX_SIZE。
  6. linux硬盘保护卡,在学校机房联想硬盘保护下安装Linux,并配置锐捷客户端
  7. 启动多个pid_西门子S7-300PLC实现PID控制
  8. Seeduino XIAO开发板安装Arduino软件包以及USB串口驱动
  9. 2021年春季学期-信号与系统-第七次作业参考答案-第四小题
  10. 第十六届全国大学生智能车竞赛赛题规划