这是个看脸的时代,如果你没有个Beautiful Face(B脸),都不好意思写博客。继上一次我通宵加班(钥匙锁家里了,门也砸了)给大家介绍了UC浏览器基本布局的实现(轻点这里),帅气的我又来水文了。今天我们将实现UC浏览器主页的交互,No picture you say a ...
相似度是不是很高O(∩_∩)O? 如果你喜欢或者想和我一起完成这个项目,请github一波(欢迎star):
https://github.com/zibuyuqing/UCBrowser
由于页面管理(堆叠视图)实现起来很复杂,我放到下一篇水文中。 这篇文章将按照以下顺序讲故事:
1.UCRootView简介(照顾没看过第一篇的同学:轻点这里)
2.自定义基本父布局(BaseLayout)
3.网页导航栏(HeadLayout)视图更新
4.贝塞尔背景(BezierLayout)实现
5.底部菜单栏(Bottombar)视图更新
6.其他view简析
下面开始表演
UCRootView是这些可滑动布局(除了堆叠视图)的Parent,里面重写了onInterceptTouchEvent和onTouchEvent方法,定义了通用滑动接口:
- public interface ScrollStateListener {
- void onStartScroll();
- void onScroll(float rate);
- void onEndScroll();
- void onTouch(float x, float y); //手指位置
- }
滑动状态通过滑动距离与目标距离之间的比值rate来表示。在这个布局下,所有子布局的位置、大小,透明度等属性通过rate来控制。今天重点不是讲这个,如果想具体了解,请点这里。
从上图可以看出,BaseLayout是UC浏览器主页面所有View组件的父类,里面定义了基本的移位(translate,目前只针对Y方向)、缩放(scale)、渐变(alpha)等方法,所有的view的属性变化都是基于Rootview传过来的rate计算得到,子view通过扩展父类实现不同场景的界面切换效果。
控制开关:mTranslateEnable 方向:Y 初始化:
- /**
- *
- * @param from 起始位置
- * @param to 最终位置
- */
- public void initTranslationY(int from, int to) {
- mFromPosition = from;
- mToPosition = to;
- setTranslationY(from);
- mDistance = from - to;
- }
计算TransY:
- /**
- *
- * @param rate 滑动的相对比率
- * @return
- */
- private float calculateTransY(float rate) {
- Log.i(TAG, "rate :: =;" + rate);
- return mFromPosition + mDistance * rate;
- }
调用:在 onScroll(rate)方法中调用 setTranslationY(calculateTransY(rate));
控制开关:mScaleEnable 方向:X,Y 初始化:
- public void initScale(float startScale, float endScale) {
- mStartScale = startScale;
- mEndScale = endScale;
- setScaleX(startScale);
- setScaleY(startScale);
- mScale = endScale - startScale;
- }
计算Scale:
- private float calculateScale(float rate) {
- return mStartScale + mScale * rate;
- }
调用:
- private void setScaleXY(float rate) {
- setScaleX(calculateScale(rate));
- setScaleY(calculateScale(rate));
- }
渐变同上,很简单。这样就把最基本的父布局写完了O(∩_∩)O哈哈~,然后我们就要写各个部件的更新啦。
这张图展示了UC浏览器HeadLayout布局结构,其中的BezierLayout包裹了天气栏、搜索栏、导航栏,其下方是网站列表,“刷新进入UC头条”提示默认不可见。现在打开手机UC浏览器,一起向上滑,预备,走!我们看到,当向上滑时,整个layout逐渐变小,并且小幅度向上移动,同时变黑,怎么实现这个效果呢?我们来分解一下:
变小 —— scale,上移 —— transY,变黑 —— foreground(黑色)改变Alpha渐显。
- mUCHeadLayout.setTranslateEnable(true); // 可移动
- mUCHeadLayout.initTranslationY(0, -100); // 小幅移动
- @Override
- public void onStartScroll() {
- // mUCCoverLayout 为下拉时所看到的提示“上滑进入UC头条”的布局
- mUCCoverLayout.setVisibility(VISIBLE);
- mUCCoverLayout.setAlpha(0.f);
- super.onStartScroll();
- }
- @Override
- public void onScroll(float rate) {
- if(rate > 0) {
- // 下拉
- // 显示提示语并下移
- mCoverTip.setTranslationY(100 * Math.abs(rate));
- // 提示布局逐渐显现
- mUCCoverLayout.setAlpha(rate * 1.5f);
- } else {
- // 上滑
- // 隐藏提示布局
- mUCCoverLayout.setVisibility(GONE);
- // foreground 逐渐显现,布局变黑
- mForeground.setAlpha((int) (ALPHA_255 * Math.abs(rate)));
- float adjustRate = 1.0f + rate * 0.05f;
- // 布局内容逐渐变小
- mCategoryContain.setScaleX(adjustRate);
- mCategoryContain.setScaleY(adjustRate);
- mwebsiteContain.setScaleX(adjustRate);
- mWebsiteContain.setScaleY(adjustRate);
- }
- super.onScroll(rate);
- }
正如之前所说,整个过程受相对滑动比率rate控制,这样就实现了我们的HeadLayout界面更新,接下来我们看其下拉时背景效果实现。
关于贝塞尔的相关知识感兴趣的可以百度一波哈。 我想告诉你ScrollStateListener接口中的onTouch(float x, float y)方法就是为了这货添加的,因为我们在开始构建贝塞尔曲线的时候要有控制点,总不能写死吧。当用户向下滑动时,我们动态的更新布局大小(Y方向),并把我们的控制点始终放在底部,贝塞尔曲线的起始点和终止点高度(Y)更新慢于控制点,就可以达到效果了。
- mPaint = new Paint();
- mPaint.setColor(mThemeColor);
- mPaint.setAntiAlias(true); // 抗锯齿
- mPaint.setStyle(Paint.Style.FILL); // 填充
- mEdgeHeight = mHeight; // mEdgeHeight 为左右两个边的高度
- mControlPoint = new Point(0,mHeight); // 初始位置为整个视图的底部,这样一开始画出来是条直线
- @Override protected void dispatchDraw(Canvas canvas) {
- drawBg(canvas);
- super.dispatchDraw(canvas);
- }
- private void drawBg(Canvas canvas) {
- mPath.reset();
- // 顶部开始
- mPath.moveTo(0, 0);
- mPath.lineTo(0, mEdgeHeight);
- // 贝塞尔曲线
- mPath.quadTo(mControlPoint.x, mControlPoint.y, mScreenWidth, mEdgeHeight);
- mPath.lineTo(mScreenWidth, 0);
- // 闭合
- mPath.lineTo(0, 0);
- canvas.drawPath(mPath, mPaint);
- }
这里用到的drawPath方法,大家可以详细了解一下Path的用法,很多炫酷效果是用这货弄的。
当手指下滑时,我们开始变弯了。
- @Override
- public void onStartScroll() {
- mStartScroll = true;
- super.onStartScroll();
- }
- public void touch(float x, float y) {
- mControlPoint.set((int) x, mControlPoint.y);
- invalidate();
- }
- @Override public void onScroll(float rate) {
- if (!mStartScroll) {
- return;
- }
- // 获取 LayoutParams 根据滑动状态动态更新视图大小
- if (mLayoutParams == null) {
- mLayoutParams = getLayoutParams();
- }
- if (rate >= 0) {
- // 下拉
- // FINAL_DISTANCE 为最大能滑动的距离
- int dis = (int)(FINAL_DISTANCE * rate);
- // 左右边界更新速度是控制点的0.5倍
- mEdgeHeight = (int)(mHeight + dis * 0.5f);
- //控制点更新
- mControlPoint.set(mControlPoint.x, mHeight + dis);
- // 视图内容改变大小,位置和透明度
- mContain.setScaleX(1.0f - rate * 0.2f);
- mContain.setScaleY(1.0f - rate * 0.2f);
- mContain.setTranslationY(dis * 0.5f);
- mContain.setAlpha(1.0f - rate * 1.5f);
- } else {
- // 上滑
- mControlPoint.set(0, mHeight);
- }
- // 改变视图大小
- mLayoutParams.height = mControlPoint.y;
- setLayoutParams(mLayoutParams);
- requestLayout();
- super.onScroll(rate);
- }
- @Override
- public void onEndScroll() {
- mStartScroll = false;
- super.onEndScroll();
- }
上面注释的很详细,我们来看一下效果,不虚,不虚 O(∩_∩)O
底部菜单栏视图更新原理和上面一样,只不过要根据menu位置计算更新速率,还有横向滑动。效果可以看前面的高清无码动图,这里我直接贴代码了。
- private float calculateMenuBtnTransX(float rate) {
- float dis = mScreenWidth / 5;
- return - dis * rate;
- }
上滑时我们会看到新闻按钮(头条、视频、订阅)会根据不同速率依次滑动到指定位置,计算方法如下:
- private float calculateNewsBtnTransY(int finalY, float rate, float velocity) {
- // velocity 是调整速率
- float adjustRate = rate * velocity;
- rate = adjustRate < -1.0f ? -1.0f: adjustRate;
- return 0 + finalY * rate;
- }
- private float calculateBtnAlpha(float rate) {
- return 1.0f + rate;
- }
- @Override public void onScroll(float rate) {
- Log. (TAG, "onScroll :: rate =:" + rate);
- if (rate > 0) {
- return;
- }
- //第1,2,4个按钮渐隐
- ivForward.setAlpha(calculateBtnAlpha(rate));
- ivBack.setAlpha(calculateBtnAlpha(rate));
- flWindowNum.setAlpha(calculateBtnAlpha(rate));
- // 第三个按钮移动到第四个位置
- ivMenu.setTranslationX(calculateMenuBtnTransX(rate));
- // 新闻按钮依次上升出现
- tvSubscribe.setTranslationY(calculateNewsBtnTransY(mHalfHeight, rate, 1.0f));
- tvVideo.setTranslationY(calculateNewsBtnTransY(mHalfHeight, rate, 1.5f));
- tvHeadline.setTranslationY(calculateNewsBtnTransY(mHalfHeight, rate, 2.0f));
- }
除了以上view组件,我们还要顶部搜索条(searchbar),新闻分类标签(newsTab)的更新没说呢,别急哈,很简单,用BaseLayout做这两个view组件的父布局,然后init一波就可以了,以searchbar为例
- <?xml version="1.0" encoding="utf-8"?>
- <com.zibuyuqing.ucbrowser.base.BaseLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:background="@color/themeBlue"
- android:padding="8dp"
- android:layout_height="@dimen/dimen_48dp">
- <ImageView
- android:layout_gravity="center"
- android:src="@drawable/ic_searchbar_book"
- style="@style/SearchBoxImageIconStyle"/>
- <TextView
- android:gravity="center"
- android:textSize="13dp"
- android:textColor="@color/windowBg"
- android:text="UC头条"
- android:layout_width="wrap_content"
- android:layout_height="match_parent" />
- <LinearLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:background="@drawable/search_box_bg"
- android:padding="6dp"
- android:layout_marginLeft="8dp"
- android:layout_height="32dp">
- <ImageView
- android:layout_gravity="center_vertical"
- android:layout_width="14dp"
- android:layout_height="14dp"
- android:layout_alignParentStart="true"
- android:layout_marginLeft="8dp"
- android:src ="@drawable/ic_search" />
- <TextView
- android:textColor="@color/windowBg"
- android:gravity="center"
- android:layout_marginLeft="8dp"
- android:textSize="13dp"
- android:text="搜索你感兴趣的内容"
- android:layout_width="wrap_content"
- android:layout_height="match_parent" />
- </LinearLayout>
- </com.zibuyuqing.ucbrowser.base.BaseLayout>
- // 可移动
- mTopSearchBar.setTranslateEnable(true);
- // 这方法是在view layout 之后获取大小,避免获取的大小全是 0
- mTopSearchBar.getViewTreeObserver().addOnGlobalLayoutListener(
- new ViewTreeObserver.OnGlobalLayoutListener() {
- @Override
- public void onGlobalLayout() {
- mTopSearchBar.getViewTreeObserver().removeOnGlobalLayoutListener(this);
- // 初始化移动参数
- mTopSearchBar.initTranslationY(-mTopSearchBar.getHeight(), 0);
- }
- }
- );
大功告成,是不是很简单
文章写得很详细,能看到这个地方也算难为你了,给你点赞(也给我点个吧,顺手牵个赞O(∩_∩)O),如果你喜欢我的博客,请关注一下,我会不断将有意思的事情写出来,你也可以给我发一些需求,我来帮你实现或者给些建议,大家一起学习一起进步。 项目地址:github.com/zibuyuqing/…,欢迎踩踏。
下篇文章我们将探讨堆叠视图的实现,敬请期待
转载请注明:juejin.im/post/5a291a…
上一篇:尝试写个UC浏览器(布局篇)
来源: https://juejin.im/post/5a291aeb51882533d022f6ba