• Android RecyclerView 之 吸顶效果


    前言

    上一篇文章已经实现了列表跟宫格布局的动态切换,这篇文章主要来说通过 CoordinatorLayoutAppbarLayout 的配合,以及 NestedScrollView 来实现吸顶效果 。效果如下。
     

    一、CoordinatorLayout 是什么?

    CoordinatorLayout 是 Android Support Library (安卓支持库) 中的一个布局容器,用于实现协调子 View 之间的交互和动画效果。它是基于观察者模式设计的,可以根据子 View 之间的关系和事件,实现协调和控制子 View 的行为,它允许你在一个复杂的应用界面中实现各种协调动作和交互效果。它是支持 Material Design 的一个重要组件,可以用于创建各种复杂的布局和动画效果。

    二、AppbarLayout 是什么?

    AppBarLayout 是 Android Support Library (安卓支持库) 中的一个布局容器,通常结合 Toolbar 和 CollapsingToolbarLayout 使用,用于实现可折叠的应用栏效果。它提供了一种简便的方式来实现应用栏的滚动和折叠效果。它在内部做了很多滚动的事件的封装。CollapsingToolbarLayout 也是安卓支持库中的一个容器,用于实现可折叠的应用栏效果。

    三、NestedScrollView 是什么?

    NestedScrollView 是 Android Support Library (安卓支持库) 中的一个可嵌套滚动的视图容器,它扩展自 ScrollView,能够在嵌套滚动的情况下,处理多个滚动视图之间的滚动冲突。

    四、实践

    所以不难理解,当 CoordinatorLayout 和 AppBarLayout 配合使用时,实现吸顶效果。通常的布局结构是:CoordinatorLayout 作为最外层容器,内部包含一个 AppBarLayout,AppBarLayout 内部包含一个可滚动的视图NestedScrollView。通过这样的布局结构,当用户滚动 NestedScrollView 时,AppBarLayout 中的 Toolbar 和 CollapsingToolbarLayout 会根据滚动的位置来调整自身的显示状态。当向上滚动时,AppBarLayout 进行折叠,Toolbar 可以隐藏,当向下滚时,AppBarLayout 展开,Toolbar 显示出来。为了实现吸顶效果,需要在 AppBarLayout 中 CollapsingToolbarLayout 上添加一个属

     app:layout_scrollFlags="scroll|enterAlways|snap"

    其中,"scroll" 表示支持滚动,"enterAlways" 表示当向下滚动时,始终进入可见状态,"snap" 表示当滚动事件结束时,自动将 Toolbar 完全显示或隐藏。,为了实现 NestedScrollView 的吸顶效果,可以在NestedScrollView 的父容器上设置属性

    app:layout_behavior="@string/appbar_scrolling_view_behavior"

    这样可以将 NestedScrollView 与 AppBarLayout 关联起来,以实现滚动时的协调效果。接下来就来一步步实现

    1.activity_news_info.xml

    新建一个 activity ,命名为 NewsInfoActivity,用作文章详情页展示,我们主要看到吸顶布局结构

    CoordinatorLayout 包含着 AppBarLayout ,AppBarLayout 包含着 CollapsingToolbarLayout 可折叠布局 其中包含着自定义 header 头布局,在 CollapsingToolbarLayout外,AppBarLayout 还包含着 自定义 inside_fixed_bar 布局 即固定悬浮框,在之外就是 NestedScrollView 包含 RecyclerView 列表,这样大体的吸顶效果的布局就搭建完毕。可扩展性很强。布局如下,

    1. "1.0" encoding="utf-8"?>
    2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    3. xmlns:app="http://schemas.android.com/apk/res-auto"
    4. android:layout_width="match_parent"
    5. android:layout_height="match_parent"
    6. android:orientation="vertical">
    7. <per.goweii.actionbarex.ActionBarEx
    8. android:id="@+id/abc_main_return"
    9. android:layout_width="match_parent"
    10. android:layout_height="wrap_content"
    11. android:background="#14a4fb"
    12. app:ab_autoImmersion="false"
    13. app:ab_bottomLineColor="#f3f3f3"
    14. app:ab_bottomLineHeight="0dp"
    15. app:ab_statusBarColor="#00000000"
    16. app:ab_statusBarMode="dark"
    17. app:ab_statusBarVisible="true"
    18. app:ab_titleBarHeight="50dp"
    19. app:ab_titleBarLayout="@layout/top" />
    20. <androidx.coordinatorlayout.widget.CoordinatorLayout
    21. android:layout_width="match_parent"
    22. android:layout_height="match_parent"
    23. android:fitsSystemWindows="false">
    24. <com.google.android.material.appbar.AppBarLayout
    25. android:id="@+id/app_bar"
    26. android:layout_width="match_parent"
    27. android:layout_height="wrap_content"
    28. android:fitsSystemWindows="false">
    29. <com.google.android.material.appbar.CollapsingToolbarLayout
    30. android:id="@+id/toolbar_layout"
    31. android:layout_width="match_parent"
    32. android:layout_height="wrap_content"
    33. android:fitsSystemWindows="false"
    34. app:layout_scrollFlags="scroll|exitUntilCollapsed"
    35. app:statusBarScrim="@android:color/transparent">
    36. <include layout="@layout/header" />
    37. com.google.android.material.appbar.CollapsingToolbarLayout>
    38. <include layout="@layout/inside_fixed_bar" />
    39. com.google.android.material.appbar.AppBarLayout>
    40. <androidx.core.widget.NestedScrollView
    41. android:layout_width="match_parent"
    42. android:layout_height="match_parent"
    43. app:layout_behavior="@string/appbar_scrolling_view_behavior">
    44. <androidx.recyclerview.widget.RecyclerView
    45. android:id="@+id/recycler_view"
    46. android:layout_width="match_parent"
    47. android:layout_height="match_parent" />
    48. androidx.core.widget.NestedScrollView>
    49. androidx.coordinatorlayout.widget.CoordinatorLayout>
    50. LinearLayout>

    2.CommentsAdapter

    接下来就是吸顶后展示的列表数据适配器

    1. public class CommentsAdapter extends BaseQuickAdapter<NewsCommentBean.Comments, BaseViewHolder> {
    2. public CommentsAdapter(int layoutResId, @Nullable List data) {
    3. super(layoutResId, data);
    4. }
    5. @Override
    6. protected void convert(@NonNull BaseViewHolder helper, NewsCommentBean.Comments item) {
    7. try {
    8. helper.setText(R.id.comment_author_tv, item.getAuthor());
    9. helper.setText(R.id.comment_info_tv, item.getContent());
    10. helper.setText(R.id.comment_link_tv, item.getLikes());
    11. String s = App.dateToStamp(item.getTime());
    12. helper.setText(R.id.comment_time_tv, s);
    13. String avatar = item.getAvatar();
    14. ImageView iconImg = helper.getView(R.id.comment_icon_img);
    15. //Glide设置圆形图片
    16. RequestOptions options = new RequestOptions().circleCropTransform();
    17. Glide.with(mContext).load(avatar).apply(options).into(iconImg);
    18. } catch (ParseException e) {
    19. e.printStackTrace();
    20. }
    21. }
    22. }

    3.comments_item_layout.xml

    有适配就需要有子布局item

    1. "1.0" encoding="utf-8"?>
    2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    3. android:layout_width="match_parent"
    4. android:layout_height="160dp"
    5. android:orientation="horizontal">
    6. <LinearLayout
    7. android:layout_width="0dp"
    8. android:layout_height="match_parent"
    9. android:layout_weight="1"
    10. android:gravity="center_horizontal">
    11. <ImageView
    12. android:id="@+id/comment_icon_img"
    13. android:layout_width="60dp"
    14. android:layout_height="60dp" />
    15. LinearLayout>
    16. <LinearLayout
    17. android:layout_width="0dp"
    18. android:layout_height="match_parent"
    19. android:layout_weight="3"
    20. android:orientation="vertical">
    21. <RelativeLayout
    22. android:layout_width="match_parent"
    23. android:layout_height="0dp"
    24. android:layout_weight="1"
    25. android:orientation="horizontal">
    26. <TextView
    27. android:id="@+id/comment_author_tv"
    28. android:layout_width="wrap_content"
    29. android:layout_height="wrap_content"
    30. android:layout_alignParentLeft="true"
    31. android:layout_centerVertical="true"
    32. android:layout_marginLeft="@dimen/dp_10"
    33. android:text="唐吉坷德" />
    34. RelativeLayout>
    35. <LinearLayout
    36. android:layout_width="match_parent"
    37. android:layout_height="wrap_content">
    38. <TextView
    39. android:id="@+id/comment_info_tv"
    40. android:layout_width="wrap_content"
    41. android:layout_height="wrap_content"
    42. android:layout_marginLeft="@dimen/dp_10"
    43. android:text="波兰,尼日利亚都木有劲本届杯赛"
    44. android:textStyle="bold" />
    45. LinearLayout>
    46. <RelativeLayout
    47. android:layout_width="match_parent"
    48. android:layout_height="0dp"
    49. android:layout_weight="1"
    50. android:gravity="center_vertical"
    51. android:orientation="vertical">
    52. <TextView
    53. android:id="@+id/comment_time_tv"
    54. android:layout_width="wrap_content"
    55. android:layout_height="wrap_content"
    56. android:layout_alignParentLeft="true"
    57. android:layout_centerVertical="true"
    58. android:layout_marginLeft="@dimen/dp_10"
    59. android:text="48分钟前" />
    60. <TextView
    61. android:id="@+id/comment_link_tv"
    62. android:layout_width="wrap_content"
    63. android:layout_height="wrap_content"
    64. android:layout_centerVertical="true"
    65. android:layout_marginRight="@dimen/dp_10"
    66. android:layout_toLeftOf="@+id/zan_img"
    67. android:text="50" />
    68. <ImageView
    69. android:id="@+id/zan_img"
    70. android:layout_width="20dp"
    71. android:layout_height="20dp"
    72. android:layout_alignParentRight="true"
    73. android:layout_centerVertical="true"
    74. android:layout_marginRight="10dp"
    75. android:background="@mipmap/zan" />
    76. <View
    77. android:layout_width="match_parent"
    78. android:layout_height="0.5dp"
    79. android:layout_alignParentBottom="true"
    80. android:layout_marginLeft="5dp"
    81. android:layout_marginTop="5dp"
    82. android:layout_marginRight="5dp"
    83. android:background="@color/light_gray" />
    84. RelativeLayout>
    85. LinearLayout>
    86. LinearLayout>

    4.NewsInfoActivity

     在上一篇文章中 添加个子项点击事件 进入到 NewsInfoActivity 当中,并且传入相关的url以及新闻id以及新闻标题,此处要注意下为什么要把点击事件单独写在一个方法里呢 ,因为我在运行的时候在切换宫格和列表后子项点击事件失效,所以封装在一个方法里后,重新再调用方法即可

    1. private void adaperChick() {
    2. adapter.setOnItemChildClickListener(new BaseQuickAdapter.OnItemChildClickListener() {
    3. @Override
    4. public void onItemChildClick(BaseQuickAdapter adapter, View view, int position) {
    5. //新闻ID
    6. String news_id = mList.get(position).getNews_id();
    7. //新闻网址
    8. String url = mList.get(position).getUrl();
    9. //新闻标题
    10. String title = mList.get(position).getTitle();
    11. Intent intent = new Intent(MainActivity.this, NewsInfoActivity.class);
    12. intent.putExtra("url", url);
    13. intent.putExtra("news_id", news_id);
    14. intent.putExtra("title", title);
    15. startActivity(intent);
    16. }
    17. });
    18. }

    1. public class NewsInfoActivity extends BaseActivity {
    2. private String news_id;
    3. private TextView infoTv;
    4. private RecyclerView recyclerView;
    5. private LinearLayoutManager linearLayoutManager;
    6. // private NormalAdapter normalAdapter;
    7. private TextView titleTv;
    8. private String title;
    9. private TextView titleInfoTv;
    10. private ImageView btnImg;
    11. private List recentList = new ArrayList<>();
    12. private LinearLayoutManager layoutManager;
    13. private CommentsAdapter adapter;
    14. private TextView numTv;
    15. private LinearLayout backLayoput;
    16. @Override
    17. protected void onCreate(Bundle savedInstanceState) {
    18. super.onCreate(savedInstanceState);
    19. setContentView(R.layout.activity_news_info);
    20. Intent intent = getIntent();
    21. news_id = intent.getStringExtra("news_id");
    22. title = intent.getStringExtra("title");
    23. infoTv = findViewById(R.id.info_tv);
    24. initData(news_id);
    25. initView();
    26. initComments(news_id);
    27. titleTv.setText("文章详情");
    28. titleInfoTv.setText(title);
    29. btnImg.setVisibility(View.GONE);
    30. layoutManager = new LinearLayoutManager(NewsInfoActivity.this);
    31. recyclerView.setLayoutManager(layoutManager);
    32. recyclerView.setNestedScrollingEnabled(false);
    33. adapter = new CommentsAdapter(R.layout.comments_item_layout, recentList);
    34. recyclerView.setAdapter(adapter);
    35. }
    36. private void initComments(String id) {
    37. Retrofit retrofit = new Retrofit.Builder()
    38. .baseUrl("https://news-at.zhihu.com/")
    39. //设置数据解析器
    40. .addConverterFactory(GsonConverterFactory.create())
    41. .build();
    42. ApiUrl apiUrl = retrofit.create(ApiUrl.class);
    43. Call comment = apiUrl.getComment(id);
    44. comment.enqueue(new Callback() {
    45. @Override
    46. public void onResponse(Call call, Response response) {
    47. NewsCommentBean body = response.body();
    48. Gson gson = new Gson();
    49. String s = gson.toJson(body);
    50. List recent = body.getRecent();
    51. if (recent.size() > 0) {
    52. try {
    53. recentList.addAll(recent);
    54. adapter.setNewData(recentList);
    55. runOnUiThread(new Runnable() {
    56. @Override
    57. public void run() {
    58. numTv.setText(recent.size() + "");
    59. }
    60. });
    61. } catch (Exception e) {
    62. String message = e.getMessage();
    63. e.printStackTrace();
    64. }
    65. }
    66. }
    67. @Override
    68. public void onFailure(Call call, Throwable t) {
    69. }
    70. });
    71. }
    72. private void initView() {
    73. recyclerView = findViewById(R.id.recycler_view);
    74. titleTv = findViewById(R.id.top_tv_function);
    75. titleInfoTv = findViewById(R.id.title_tv);
    76. btnImg = findViewById(R.id.btn_main_menu);
    77. numTv = findViewById(R.id.num_tv);
    78. backLayoput = findViewById(R.id.btn_back_layout);
    79. backLayoput.setOnClickListener(new View.OnClickListener() {
    80. @Override
    81. public void onClick(View v) {
    82. finish();
    83. }
    84. });
    85. }
    86. private void initData(String news_id) {
    87. Retrofit retrofit = new Retrofit.Builder()
    88. .baseUrl("https://news-at.zhihu.com/")
    89. //设置数据解析器
    90. .addConverterFactory(GsonConverterFactory.create())
    91. .build();
    92. ApiUrl apiUrl = retrofit.create(ApiUrl.class);
    93. Call newsInfoBean = apiUrl.getNewsInfoBean(news_id);
    94. newsInfoBean.enqueue(new Callback() {
    95. @Override
    96. public void onResponse(Call call, Response response) {
    97. NewsInfoBean body = response.body();
    98. String body1 = body.getBody();
    99. Document doc = Jsoup.parse(body1);
    100. Elements elements = doc.select("div.content"); //获取
      里的内容
    101. for (Element element : elements) {
    102. String text = element.text(); //获取标签内的文本内容
    103. infoTv.setText(text); //将解析出来的文本内容设置到TextView上
    104. }
    105. }
    106. @Override
    107. public void onFailure(Call call, Throwable t) {
    108. }
    109. });
    110. }
    111. }

    总结

    一个小小的很实用的功能就完成了,下一篇文章会接着实现RecyclerView 多布局效果,后续还会加上列表本地缓存等功能,Demo 在此系列文章完结附上,不妨点赞收藏哦~

    青山不改,绿水长流 有缘江湖再见 ~

  • 相关阅读:
    一文搞懂UART通信协议
    python-opencv 之开运算、闭运算、形态学梯度、“礼帽”和“黑帽”
    建设网站-个人电子图书馆
    Hive CLI和Beeline命令行的基本使用
    IDEA乱码问题解决
    分享:第十届“泰迪杯”数据挖掘挑战赛优秀作品--A1-基于深度学习的农田害虫定位与识别研究(一)
    需求管理手册-需求分类与目标(5)
    安装单机版redis
    Ruby langchainrb gem and custom configuration for the model setup
    深入浅出Dart》函数
  • 原文地址:https://blog.csdn.net/X_sunmmer/article/details/132561672