Android模仿实现微博详情页滑动固定顶部栏的效果实例

前言

最近项目中遇到一个需求,类似微博详情页的效果,通过查找相关的资料终于找了对应的解决方案,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧。

先来看下我们今天要实现的效果:


滑动固定顶部栏效果图

这段时间公司准备重构一个项目,刚好用到这个效果,我就顺带写了篇文章,关于这个效果网上可以找到一些相关资料的,昨晚看了一些,感觉都不是很好,有点模棱两可的样子,也没提到需要注意的一些关键点,这里来做下整理,由于涉及到公司的代码,这里我就写个简单的Demo来讲解。


简单Demo

传统套路:

写两个一模一样的固定栏,外层用帧布局(FrameLayout)包裹,然后把外层的固定栏先隐藏,当内层的固定栏滑动到外层固定栏位置的时候,把内层固定栏隐藏,外层的固定栏显示,反之滑回来的时候把外层固定栏隐藏,内存固定栏显示。


传统套路图

这样做的有几个不好的地方:

1、重复写了一样的布局,在XML渲染的时候耗费了性能(比如更多次的测量,布局等)

2、当页面快速滚动的时候可能出现一系列的问题(布局重复,闪烁)

3、当这个固定布局带有状态的时候,逻辑会变得很复杂,比如上面那张GIF动图,固定栏中带有筛选分类,地区,年月信息,如果按照传统套路来写,那么在内层固定栏隐藏的时候需要把状态记录并且带给外层固定栏,而且相对应很多动作监听事件也需要写多次。

新套路:

这里我换了一种思路,大体布局还是不变的,只是把两个固定栏简化成了一个,只是利用removeView和addView根据坐标点在页面滑动的时候动态的把固定栏在内外部切换,这样做的好处很好的解决了上面提到的1、2点问题,当然在快速的removeView和addView还是会出现页面闪烁不自然的问题,后面会提到解决的小窍门。

先来看下XML布局:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
 xmlns:android="http://schemas.android.com/apk/res/android"
 xmlns:tools="http://schemas.android.com/tools"
 android:layout_width="match_parent"
 android:layout_height="match_parent">

 <com.lcw.view.FixedHeaderScrollView.ObservableScrollView
 android:id="@+id/sv_contentView"
 android:layout_width="match_parent"
 android:layout_height="match_parent"
 android:scrollbars="none"
 >
 <LinearLayout
 android:id="@+id/ll_contentView"
 android:layout_width="match_parent"
 android:layout_height="wrap_content"
 android:orientation="vertical">
 <TextView
 android:id="@+id/tv_headerView"
 android:layout_width="match_parent"
 android:layout_height="200dp"
 android:text="我是头部布局"
 android:textSize="30sp"
 android:background="#ad29e1"
 android:gravity="center"/>
 <LinearLayout
 android:id="@+id/ll_topView"
 android:layout_width="match_parent"
 android:layout_height="50dp"
 android:gravity="center"
 android:orientation="vertical">
 <TextView
  android:id="@+id/tv_topView"
  android:layout_width="match_parent"
  android:layout_height="50dp"
  android:text="我是内层固定的布局"
  android:background="#3be42f"
  android:textSize="30sp"
  android:gravity="center"/>
 </LinearLayout>

 <TextView
 android:id="@+id/tv_contentView"
 android:layout_width="match_parent"
 android:layout_height="1000dp"
 android:text="我是内容布局"
 android:textSize="30sp"
 android:background="#dc7f28"
 android:paddingTop="160dp"
 android:gravity="top|center_horizontal"/>

 </LinearLayout>
 </com.lcw.view.FixedHeaderScrollView.ObservableScrollView>

 <LinearLayout
 android:id="@+id/ll_fixedView"
 android:layout_width="match_parent"
 android:layout_height="50dp"
 android:orientation="vertical"/>

</FrameLayout>

这里和上面提到的一样,最外层用了FrameLayout(RelativeLayout也可以)包裹着一个ScrollView和一个LinearLayout,当我们页面滑动到指定点的时候,需要把内层的“我是内层固定布局”移除,同时添加到外层的ViewGroup(LinearLayout)中。

自定义ScrollView,利用回调接口的方式使滑动数据对外暴露:

虽然谷歌官方给ScrollView提供了一个设置滑动监听方法setOnScrollChangeListener,不过这个方法需要基于API23之上(Android6.0系统),在日常开发中,我们需要对老系统用户进行兼容(当前兼容版本为Android4.1系统以上),所以这里我们需要去继承ScrollView并把这个监听事件通过接口的方式对外暴露,这里把这个View取名为ObservableScrollView。

package com.lcw.view.FixedHeaderScrollView;

import android.content.Context;
import android.util.AttributeSet;
import android.widget.ScrollView;

/**
 * 监听ScrollView的滑动数据
 * Create by: chenwei.li
 * Date: 2017/8/21
 * time: 11:36
 * Email: lichenwei.me@foxmail.com
 */
public class ObservableScrollView extends ScrollView{

 public ObservableScrollView(Context context) {
 this(context,null);
 }

 public ObservableScrollView(Context context, AttributeSet attrs) {
 this(context, attrs,0);
 }

 public ObservableScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
 super(context, attrs, defStyleAttr);
 }

 private OnObservableScrollViewScrollChanged mOnObservableScrollViewScrollChanged;

 public void setOnObservableScrollViewScrollChanged(OnObservableScrollViewScrollChanged mOnObservableScrollViewScrollChanged) {
 this.mOnObservableScrollViewScrollChanged = mOnObservableScrollViewScrollChanged;
 }

 public interface OnObservableScrollViewScrollChanged{
 void onObservableScrollViewScrollChanged(int l, int t, int oldl, int oldt);
 }

 /**
 * @param l Current horizontal scroll origin. 当前滑动的x轴距离
 * @param t Current vertical scroll origin. 当前滑动的y轴距离
 * @param oldl Previous horizontal scroll origin. 上一次滑动的x轴距离
 * @param oldt Previous vertical scroll origin. 上一次滑动的y轴距离
 */
 @Override
 protected void onScrollChanged(int l, int t, int oldl, int oldt) {
 super.onScrollChanged(l, t, oldl, oldt);
 if(mOnObservableScrollViewScrollChanged!=null){
 mOnObservableScrollViewScrollChanged.onObservableScrollViewScrollChanged(l,t,oldl,oldt);
 }
 }
}

这里就可以开始写我们的调用类了

package com.lcw.view.FixedHeaderScrollView;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.widget.LinearLayout;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity implements ObservableScrollView.OnObservableScrollViewScrollChanged{

 private ObservableScrollView sv_contentView;
 private LinearLayout ll_topView;
 private TextView tv_topView;
 private LinearLayout ll_fixedView;

 //用来记录内层固定布局到屏幕顶部的距离
 private int mHeight;

 @Override
 protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 setContentView(R.layout.activity_main);

 sv_contentView= (ObservableScrollView) findViewById(R.id.sv_contentView);
 ll_topView= (LinearLayout) findViewById(R.id.ll_topView);
 tv_topView= (TextView) findViewById(R.id.tv_topView);
 ll_fixedView= (LinearLayout) findViewById(R.id.ll_fixedView);

 sv_contentView.setOnObservableScrollViewScrollChanged(this);

// ViewTreeObserver viewTreeObserver=ll_topView.getViewTreeObserver();
// viewTreeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
// @Override
// public void onGlobalLayout() {
// ll_topView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
// mHeight=ll_topView.getTop();
// }
// });

 }

 @Override
 public void onWindowFocusChanged(boolean hasFocus) {
 super.onWindowFocusChanged(hasFocus);
 if(hasFocus){
 //获取HeaderView的高度,当滑动大于等于这个高度的时候,需要把topView移除当前布局,放入到外层布局
 mHeight=ll_topView.getTop();
 }
 }

 /**
 * @param l Current horizontal scroll origin. 当前滑动的x轴距离
 * @param t Current vertical scroll origin. 当前滑动的y轴距离
 * @param oldl Previous horizontal scroll origin. 上一次滑动的x轴距离
 * @param oldt Previous vertical scroll origin. 上一次滑动的y轴距离
 */
 @Override
 public void onObservableScrollViewScrollChanged(int l, int t, int oldl, int oldt) {
 if(t>=mHeight){
 if(tv_topView.getParent()!=ll_fixedView){
  ll_topView.removeView(tv_topView);
  ll_fixedView.addView(tv_topView);
 }
 }else{
 if(tv_topView.getParent()!=ll_topView){
  ll_fixedView.removeView(tv_topView);
  ll_topView.addView(tv_topView);
 }
 }
 }
}

这里我们实现了ObservableScrollView.OnObservableScrollViewScrollChanged接口,当我们对ScrollView注册监听的时候,就可以在回调接口里拿到对应的滑动数据,其中第二个参数t就是滑动y轴的距离,现在我们只需要拿到固定布局到顶部的距离就可以判断什么时候需要移除和添加View了。

相关讲解:

1、首先我们需要知道,在Activity生命周期里的onCreate方法里对一个View去执行getWidth,getHeight,getTop,getBottom等一系列的方法是拿不到数据的,得到的结果都为0,由于此时Activity还没有得到焦点,依附在Activity的View自然也就得不到数据,所以我们需要在onResume后去进行对View的数据获取。

这里我们可以通过onGlobalLayoutListener或者onWidnowFocusChanged等方法去获取,这里的执行顺序是:Activity.onCreate->Activity.onResume->View.onMeasure->View.onLayout->onGlobalLayoutListener->Activity.onWidnowFocusChanged..(具体用哪个,看当前环境情况,比如在Fragment里是没有onWidnowFocusChanged,如果需要获取一个View的相关数据,就可以根据onGlobalLayoutListener来做,上面代码提供两种示例)

2、关于获取滑动的高度,首先我们来看一张图:

Andorid里关于View的坐标系

这里需要注意的是,除了getRawX和getRawY是相对屏幕的位置,其他的是相对应所在父布局的位置,所以在确定数据的时候,需要注意布局的嵌套。

3、当我们拿到所需要滑动的高度时,我们需要对固定布局进行临界值做判断(这里设当前滑动值为t,所需滑动值为y)
比如当我们界面一开始向上滑的时候t值是小于y值的,此时内部固定栏是不需要移除的,而当我们超过y值往回滑t值又小于y值的时候,此时内部固定栏是需要从外部移除添加到内部的,所以这里我们需要对固定栏所在的父布局(ViewGroup)做判断。

最后补充:


微博详情页

1、不管你的顶部固定栏布局多简单,建议在外套一层ViewGroup,这样方便addView的操作,不然需要去控制外层ViewGroup的addView的index位置。

2、确定View的宽高度数据可以借助onGlobalLayoutListener或者onWidnowFocusChanged来做,注意相对父布局的嵌套。

3、这种页面的设计最早来源于iOS的设计,在iOS里ScrollView嵌套TableView(相当于ListView)是没有问题的,但是在Android里,这样子的嵌套会导致ListView的复用机制作废,也就是会不断是去进行onMeasure的计算,执行多次Adapter里的getView,也就意味着多次的findViewById,使得ViewHolder失效。

4、这是个小技巧,在快速滑动的时候有些人会出现固定布局的闪烁,其实这个和removeView和addView有关系,如果你的ViewGroup设置成了warp_content,这是一个测量的耗时操作,这里只需要配合上面提到的第1点,给固定栏外层布局一个固定的高度值即可(与固定栏高度保持一致)。

好了,到这里就结束。

源码下载:

github源码地址:源码下载

本地下载:点击这里

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对我们的支持。

您可能感兴趣的文章:

  • Android实现滑动到顶部悬停的效果
  • Android实现顶部导航菜单左右滑动效果
  • Android 顶部标题栏随滑动时的渐变隐藏和渐变显示效果
  • Android滑动组件悬浮固定在顶部效果
  • android scrollview 滑动到顶端或者指定位置的实现方法
  • Android仿淘宝view滑动至屏幕顶部会一直停留在顶部的位置
  • Android实现顶部导航栏可点击可滑动效果(仿微信仿豆瓣网)
  • Android实现listview滑动时渐隐渐现顶部栏实例代码
  • Android滑动到顶部和底部时出现的阴影如何去掉
  • Android模仿美团顶部的滑动菜单实例代码
(0)

相关推荐

  • Android仿淘宝view滑动至屏幕顶部会一直停留在顶部的位置

    在刚刚完成的项目中,在一个页面中,用户体验师提出引用户操作的入住按钮要一直保留在页面当中,不管页面能滚动多长都得停留在页面的可视区域.最终实现效果如下图所示:   如图中的红色框中的view始终会停留在页面中,如果滑动至页面的顶部,会一直保留在顶部. 下面来说下具体的实现思路: 思路:其实整个页面当中一共有两个视觉效果一样的View,通过滑动的位置来进行View的隐藏和显示来达到这种效果.整个页面的在上下滑动的过程中可以总结为两个状态,状态A(如图1所示),view2在可视区域内时,view1不

  • Android模仿美团顶部的滑动菜单实例代码

    前言 本文主要给大家介绍了关于Android模仿美团顶部滑动菜单的相关内容,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧. 先来看下效果图: 实现方法 这是通过 ViewPager 和 GridView 相结合做出来的效果,每一个 ViewPager 页面都是一个 GridView,底部的每个滑动指示圆点都是从布局文件中 inflate 出来的 首先需要一个代表每个活动主题的 JavaBean /** * Created by CZY on 2017/6/23. */ publ

  • Android实现listview滑动时渐隐渐现顶部栏实例代码

    我在开发的时候遇到了这样的需求,就是在listview的滑动中,需要对顶部的栏目由透明慢慢的变为不透明的状态,就是以下的效果 最先开始的时候想的很简单,无非就是监听listview的滑动距离,然后根据距离算出透明度的比值,就可以了,但是事实上呢也的确是这样做的 只是在获取listview的滑动距离上可能没法直接获取,需要动态的去计算 下面贴出全部代码吧,不想码字了,最近感冒了,脑袋晕乎乎的,还疼,代码更直观一些 private void initListener() { lvList.setOn

  • Android滑动组件悬浮固定在顶部效果

    要想实现的效果是如下: 场景:有些时候是内容中间的组件当滑动至顶部的时候固定显示在顶部. 实现的思路: 1.目标组件(button)有两套,放在顶部和内容中间: 2.当内容中间的组件滑动至顶部栏位置时控制显示/隐藏顶部和中间的组件(涉及到组件获取在屏幕的位置知识点): activity代码: public class MainActivity extends AppCompatActivity implements ObservableScrollView.ScrollViewListener

  • Android滑动到顶部和底部时出现的阴影如何去掉

    android去掉滑动到顶部和底部的阴影 <ListView android:id="@+id/listView" android:layout_width="match_parent" android:layout_height="wrap_content" android:cacheColorHint="#00000000" android:divider="@color/line_color"

  • Android 顶部标题栏随滑动时的渐变隐藏和渐变显示效果

    各位早上好,话不多说,先上效果图: 注意顶部:首页TextView的变化(显示和隐藏)! 首先分析下:UI状态,其是由RecyclerView添加头部组成+RecyclerView 头部添加和RecyclerView分别引用如下:具体的分装数据的过程这里就不在说明,下篇博客会更加深入的写关于 RecyclerView总添加多种不同type类型 compile 'com.bartoszlipinski.recyclerviewheader:library:1.2.1' compile 'com.a

  • Android实现顶部导航栏可点击可滑动效果(仿微信仿豆瓣网)

    使用ViewPager,PagerSlidingTabStrip,SwipeRefreshLayout打造一款可以点击可以侧滑的顶部导航栏. 先简单介绍一下所用的两个个开源库. PagerSlidingTabStrip Github地址 用法: 1.向app Module中的build.gradle中添加依赖 dependencies { compile 'com.astuetz:pagerslidingtabstrip:1.0.1' } 2.把PagerSlidingTabStrip这个控件添

  • Android实现顶部导航菜单左右滑动效果

    本文给大家介绍在Android中如何实现顶部导航菜单左右滑动效果,具体内容如下 第一种解决方案: 实现原理是使用android-support-v4.jar包中ViewPager控件,在ViewPager控件中设置流布局,再在流布局中设置几项TextView,给每一个TextView设置相关参数,事件等.关于ViewPager控件可以设置全屏幕滑动效果,当然也可以实现局部滑动效果,下面介绍导航菜单. 关于导航菜单,相信大家对它并不陌生,比如在新闻客户端中就经常使用左右滑动菜单来显示不同类别的新闻

  • Android实现滑动到顶部悬停的效果

    先来看下要实现效果图: 查阅资料后,发现网上大部分都是用这种方法实现的: 多写一个和需要悬浮的部分一模一样的layout,先把浮动区域的可见性设置为gone.当浮动区域滑动到顶部的时候,就把浮动区域B的可见性设置为VISIBLE.这样看起来就像悬浮在顶部不动了. 这里介绍的是另外一种方式: 使用design包中的控件 <android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.

  • android scrollview 滑动到顶端或者指定位置的实现方法

    在Android开发中很多时候会遇到一屏显示不下所有内容的现象,那大家也知道这个时候肯定会想到用scrollview来进行滚屏显示. 这个时候由于某些需求,会要求在最开始显示scrollview的时候就定位到某一处,这篇就是来讲这个的哈- 首先,scrollView.scrollTo( x, y );这个方法是能对滚动条进行定位的,这个大家都知道. But,貌似很多时候这个方法的调用没有什么效果呀-- 上面所说的调用scrollTo方法看上去好像并没有起到对滚动条进行定位的效果,其实是因为我们是

随机推荐