效果

一 布局分析:

分成三部分
1.菜单栏TabView部分 本文采用线性布局包裹TextView的形式 采用LinearLayout的原因是每个Tab页可以使用权重做到均分LinearLayout的效果
2.主题内容MainContent部分 本文为了简单处理 直接放一个相对布局
3.阴影部分ShadowView 是个半透明的View
3个部分放在一个相对布局中

二 添加适配器模式并进行查漏补缺

这里的适配器为了数据和Custom FilterView能够进行适配,使用不同的Adapter可以显示不同的数据 或者如果Adapter做的强大,可以使用一个Adapter显示各种格式的数据(本文以String数组为例)
具体的适配器模式可以参考这篇文章
https://blog.csdn.net/u011109881/article/details/82288922
给tabView的每个item增加点击动作
轮巡tab页 看看是不是点击了tab页
如果点击了当前显示的tab页 关闭主体内容 切换tab页颜色
如果点击了非当前显示的tab页 tab页的颜色需要变化 内容需要变化
给阴影增加点击动作关闭菜单

三 添加动画

动画没有什么难度 调试几把就可以了 主要这里练习了AnimatorSet可以进行组合动画

四 观察者模式练习

具体的观察者模式可以参考这篇文章
https://blog.csdn.net/u011109881/article/details/81043699
需求:
一般情况下我们的内容页主要是一些列表 那么我们希望点击列表页面的时候 能够获取到当前点击的item项 并同时关闭filteView界面(调用closeContent方法),那么我们势必要在adapter调用closeContent方法 观察者模式就是为了实现这个功能而产生的。(虽然说adapter直接持有CustomFilterView对象也可以实现 但是这样耦合性提高)
具体做法可以参考ListView setAdapter的相关写法
我将ListView 和当前的Demo相似的类列出如下

观察者模式的工作过程:
CustomFilterView包含一个观察者AdapterObserver,在setAdapter的时候注册到事件源/被观察者上,AdapterObserver成为实际观察者
当事件源发生事件事 调用notifyDataSetChanged 通知每一个观察者,因为CustomFilterView持有AdapterObserver,所以能够收到通知


正如我在https://blog.csdn.net/u011109881/article/details/81043699中所描述的一样

其实Java API的设计有点问题,在Java编程经验中有一条:要面向接口编程而不是面向实现。
我们发现原先我们自己写的Subject是个接口,而JAVA
API中的Observable是个实体类,这样如果我们的被观察者继承了Observable就无法继承其他类了,这就限制了被观察者实现的实体类的扩展和复用。
另外Java编程提倡多用组合少用继承。但是Observable将changed的控制方法大多设置为protected,这就意味着,即使我们把Observable的实体类组合到Observer实体类中,也无法控制changed因为protected只在本类及其子类可用。这违反了用组合少用继承,使得程序不够灵活,有时会限制程序的扩展。
因此上面的文件我尽量声明为接口 这是与视频以及源码不同之处

五 部分代码

Activity

public class MainActivity extends AppCompatActivity {CustomFilterView mCustomFilterView;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);ViewAdapter filterViewAdapter = new ViewBaseAdapter(this);mCustomFilterView = findViewById(R.id.filterView);mCustomFilterView.setFilterViewAdapter(filterViewAdapter);}
}

观察者接口

interface Observer {//角色定位 抽象观察者 类比Observervoid notifyDataSetChanged();
}

View

class CustomFilterView extends RelativeLayout implements View.OnClickListener {//角色定位 CustomFilterView相当于ListViewprivate static final long ANIMATION_DURATION = 300;private static final String TAG = "CustomFilterView";private LinearLayout mContainerTab;private RelativeLayout mContainerContent;private View mShadowView;private ViewAdapter mFilterViewAdapter;private int mCurrentTabIndex = 0;//当前所处Tab的位置private boolean isAnimating = false;private AdapterObserver mObserver;public CustomFilterView(Context context) {this(context, null);}public CustomFilterView(Context context, @Nullable AttributeSet attrs) {this(context, attrs, 0);}public CustomFilterView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);//还是感觉使用xml创建的方式更加清晰 使用代码添加布局适合用在添加一两个View的情况 一旦View的个数增加 逻辑也变得复杂,层次不够清晰 容易出buginflate(getContext(), R.layout.layout_filter, this);mContainerTab = findViewById(R.id.container_tab);mContainerContent = findViewById(R.id.container_content);mShadowView = findViewById(R.id.view_shadow);}public void setFilterViewAdapter(ViewAdapter filterViewAdapter) {//参考ListView的setAdapterif (mFilterViewAdapter != null && mObserver != null) {mFilterViewAdapter.unregisterObserver(mObserver);}this.mFilterViewAdapter = filterViewAdapter;mObserver = new AdapterObserver();//Adapter是具体的观察者(调用registerObserver的对象是观察者)mFilterViewAdapter.registerObserver(mObserver);//获取当前有几个tab页int count = mFilterViewAdapter.getCount();for (int i = 0; i < count; i++) {//显示tabView部分TextView tabView = (TextView) mFilterViewAdapter.getTabView(i);LinearLayout.LayoutParams tabViewLayoutParams = new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);tabViewLayoutParams.weight = 1;tabView.setLayoutParams(tabViewLayoutParams);tabView.setTextColor(Color.BLACK);mContainerTab.addView(tabView);//给tabView的每个item增加点击动作tabView.setOnClickListener(this);//显示content部分 只显示第一页if (i == 0) {View contentView = mFilterViewAdapter.getContentView(i);mContainerContent.addView(contentView);}}//给第零页设置为选中状态((TextView) mContainerTab.getChildAt(mCurrentTabIndex)).setTextColor(Color.RED);((TextView) mContainerTab.getChildAt(mCurrentTabIndex)).setBackgroundColor(Color.GRAY);mShadowView.setOnClickListener(this);}@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {super.onMeasure(widthMeasureSpec, heightMeasureSpec);//设置阴影区域为整个view的1/5LayoutParams mShadowViewLayoutParams = (LayoutParams) mShadowView.getLayoutParams();mShadowViewLayoutParams.height = MeasureSpec.getSize(heightMeasureSpec) / 5;mShadowView.setLayoutParams(mShadowViewLayoutParams);}@Overridepublic void onClick(View v) {//轮巡tab页 看看是不是点击了tab页int tabCount = mFilterViewAdapter.getCount();for (int i = 0; i < tabCount; i++) {Log.d(TAG, "onClick: " + isAnimating);//点击了某一个tab页if (mContainerTab.getChildAt(i) == v) {//点击了当前显示的tab页 关闭主体内容 切换tab页颜色if (mContainerTab.getChildAt(mCurrentTabIndex) == v) {closeContent();} else if (mCurrentTabIndex == -1) {//当前没有显示的tab页openContent(i);} else {//点击了非当前显示的tab页  tab页text的颜色需要变化(注意新旧的当前页面都要变化) 内容需要变化//旧tab状态更新((TextView) mContainerTab.getChildAt(mCurrentTabIndex)).setTextColor(Color.BLACK);((TextView) mContainerTab.getChildAt(mCurrentTabIndex)).setBackgroundColor(Color.WHITE);mCurrentTabIndex = i;//新tab页状态更新((TextView) mContainerTab.getChildAt(mCurrentTabIndex)).setTextColor(Color.RED);((TextView) mContainerTab.getChildAt(mCurrentTabIndex)).setBackgroundColor(Color.GRAY);mContainerContent.removeAllViews();mContainerContent.addView(mFilterViewAdapter.getContentView(i));}}}//如果点击了shadowView 同样关闭主体部分if (v == mShadowView) {closeContent();}}private void closeContent() {if (isAnimating) {return;}isAnimating = true;((TextView) mContainerTab.getChildAt(mCurrentTabIndex)).setTextColor(Color.BLACK);((TextView) mContainerTab.getChildAt(mCurrentTabIndex)).setBackgroundColor(Color.WHITE);mCurrentTabIndex = -1;//添加旧页面关闭动画ObjectAnimator transactionAnimateInY = ObjectAnimator.ofFloat(mContainerContent, "translationY", 0, -mContainerContent.getMeasuredHeight());ObjectAnimator alphaAnimate = ObjectAnimator.ofFloat(mShadowView, "alpha", 1f, 0);AnimatorSet animatorSet = new AnimatorSet();animatorSet.setDuration(ANIMATION_DURATION);//顺序播放动画animatorSet.playTogether(transactionAnimateInY, alphaAnimate);animatorSet.start();animatorSet.addListener(new Animator.AnimatorListener() {@Overridepublic void onAnimationStart(Animator animation) {}@Overridepublic void onAnimationEnd(Animator animation) {mContainerContent.setVisibility(GONE);mShadowView.setVisibility(GONE);isAnimating = false;}@Overridepublic void onAnimationCancel(Animator animation) {}@Overridepublic void onAnimationRepeat(Animator animation) {}});}private void openContent(int tabIndex) {if (isAnimating) {return;}isAnimating = true;//新tab页状态更新mCurrentTabIndex = tabIndex;((TextView) mContainerTab.getChildAt(mCurrentTabIndex)).setTextColor(Color.RED);((TextView) mContainerTab.getChildAt(mCurrentTabIndex)).setBackgroundColor(Color.GRAY);mContainerContent.setVisibility(VISIBLE);mContainerContent.removeAllViews();mContainerContent.addView(mFilterViewAdapter.getContentView(tabIndex));//添加新页面打开动画//设置主体位置的位移动画和阴影透明度动画mShadowView.setVisibility(VISIBLE);ObjectAnimator transactionAnimateInY = ObjectAnimator.ofFloat(mContainerContent, "translationY", -mContainerContent.getMeasuredHeight(), 0);ObjectAnimator alphaAnimate = ObjectAnimator.ofFloat(mShadowView, "alpha", 0, 1f);AnimatorSet animatorSet = new AnimatorSet();animatorSet.setDuration(ANIMATION_DURATION);//顺序播放动画animatorSet.playTogether(transactionAnimateInY, alphaAnimate);animatorSet.start();animatorSet.addListener(new Animator.AnimatorListener() {@Overridepublic void onAnimationStart(Animator animation) {}@Overridepublic void onAnimationEnd(Animator animation) {isAnimating = false;}@Overridepublic void onAnimationCancel(Animator animation) {}@Overridepublic void onAnimationRepeat(Animator animation) {}});}//角色定位 具体观察者 类比ListView中的AdapterDataSetObserverclass AdapterObserver implements Observer {@Overridepublic void notifyDataSetChanged() {closeContent();}}
}

Adapter接口

public interface ViewAdapter {//角色定位 抽象的被观察者 参考ListView的Adapter.javavoid registerObserver(Observer observer);void unregisterObserver(Observer observer);void notifyContentItemClick(View view);// 获取总共有几个tab页abstract int getCount();// 获取当前tab的TabViewabstract View getTabView(int position);// 获取当前tab的菜单内容abstract View getContentView(int position);
}

Adapter实现类

class ViewBaseAdapter implements ViewAdapter {//角色定位 具体的被观察者 类比ListView的BaseAdapterprivate Context mContext;String[] tabTexts = {"美食", "生活", "鬼畜", "动漫区", "其他"};protected final ArrayList<Observer> mObservers = new ArrayList<>();@Overridepublic void registerObserver(Observer observer) {mObservers.add(observer);}@Overridepublic void unregisterObserver(Observer observer) {mObservers.remove(observer);}@Overridepublic void notifyContentItemClick(View view) {for (Observer observer : mObservers) {observer.notifyDataSetChanged();}}public ViewBaseAdapter(Context context) {this.mContext = context;}@Overridepublic int getCount() {return tabTexts.length;}@Overridepublic View getTabView(int position) {TextView textTabView = new TextView(mContext);textTabView.setPadding(10, 10, 10, 10);textTabView.setGravity(Gravity.CENTER);textTabView.setText(tabTexts[position]);return textTabView;}@Overridepublic View getContentView(int position) {TextView textContentView = new TextView(mContext);textContentView.setGravity(Gravity.CENTER);textContentView.setText(tabTexts[position]);RelativeLayout.LayoutParams contentViewLayoutParams = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT);contentViewLayoutParams.addRule(RelativeLayout.CENTER_IN_PARENT);textContentView.setLayoutParams(contentViewLayoutParams);textContentView.setOnClickListener(v -> {//事件源 具体的被观察者 这里通知其他观察者事件notifyContentItemClick(v);});return textContentView;}
}

XML

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"tools:context=".MainActivity"><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_alignParentBottom="true"android:text="Hello World!" /><com.example.customfilterview.CustomFilterViewandroid:id="@+id/filterView"android:layout_width="match_parent"android:layout_height="match_parent" /></RelativeLayout><?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"><RelativeLayoutandroid:background="#cfc"android:layout_below="@+id/container_tab"android:layout_above="@+id/view_shadow"android:id="@+id/container_content"android:layout_width="match_parent"android:layout_height="wrap_content"android:orientation="horizontal" /><LinearLayoutandroid:background="#fff"android:id="@+id/container_tab"android:layout_width="match_parent"android:layout_height="wrap_content"android:orientation="horizontal" /><Viewandroid:layout_alignParentBottom="true"android:id="@+id/view_shadow"android:layout_width="match_parent"android:layout_height="wrap_content"android:background="#88888888" /></RelativeLayout>

后记

后来在准备文章的时候测试到一些bug,当时已经很晚了,虽然有点累了 还是想在当天fix,然而花了将近1个多小时还是没有理清头绪。后来实在没有办法,只得留给第二天解决。早上看了不到五分钟就定位并解决问题了。人的大脑真实神奇,要高效的完成一项任务,充分的休息和清醒的大脑是必要的,否则只是堆砌事件反而没什么效果。

代码:
https://github.com/caihuijian/learn_darren_android

红橙Darren视频笔记 筛选View 属性动画 Adapter模式 组合动画AnimatorSet 观察者模式(对比Android ListView) 练习相关推荐

  1. 红橙Darren视频笔记 自定义View总集篇

    本节目的 了解 ActivityManagerService Activity ActivityManager Window WindowManager WindowManagerService Se ...

  2. 红橙Darren视频笔记 UML图简介

    整体架构复制自红橙原视频的课堂笔记 因为他这一课没有博客,所以没有转载链接,CSDN没有转载地址是无法作为转载类型的文章发表的,暂时标记为原创 参考链接 https://blog.csdn.net/r ...

  3. 红橙Darren视频笔记 类加载机制(API28) 自己写个热修复 查看源码网站

    第一部分 类加载机制 一个Activity是如何被Android虚拟机找到的? 在之前的文章 红橙Darren视频笔记 自定义View总集篇(https://blog.csdn.net/u011109 ...

  4. 红橙Darren视频笔记 Behavior的工作原理源码分析

    主要coordinatorlayout的代码来自coordinatorlayout-1.0.0-sources.jar 本文从源码介绍 CoordinatorLayout 的 behavior 怎么工 ...

  5. 红橙Darren视频笔记 代理模式 动态代理和静态代理

    红橙Darren视频笔记 代理模式 动态代理和静态代理(Android API 25) 关于代理模式我之前有过相关的介绍: https://blog.csdn.net/u011109881/artic ...

  6. 红橙Darren视频笔记 仿QQ侧滑效果

    这一篇没有什么新的内容 就是改写 红橙Darren视频笔记 仿酷狗侧滑效果 的侧滑的效果 1.去掉淡入淡出效果 2.加上黑色模板效果 效果: 去掉淡入淡出效果很简单 就是注释掉onScrollChan ...

  7. 红橙Darren视频笔记 ViewGroup事件分发分析 基于API27

    本节目标,通过案例,先看程序运行结果,然后跟踪源码,理解为什么会有这样的输出,继而理解view group的分发机制,感觉和证明题很像呢. 考虑以下程序的运行结果: case1: public cla ...

  8. 红橙Darren视频笔记 利用阿里巴巴AndFix进行热修复

    注意 由于AndFix在2017年左右就停止更新了,在最新版本的apk上遇到很多问题,我最终也没有成功进行热修复.本节主要是学习热修复的原理 在上一篇 红橙Darren视频笔记 自己捕获异常并保存到本 ...

  9. 红橙Darren视频笔记 view的绘制流程(上) onMeasure测量代码分析 基于API27

    一.准备工作Activity的onCreate和onResume调用过程 从ActivityThread的handleLaunchActivity开始进行代码跟踪 private void handl ...

最新文章

  1. 设计模式学习笔记:六大原则
  2. IndiaHacks 2016 - Online Edition (Div. 1 + Div. 2) A. Bear and Three Balls 水题
  3. pycharm新建文件夹时新建python package和新建directory有什么区别?
  4. linux中/bin和/sbin和/usr/bin和/usr/sbin
  5. 网络共享服务Samba和NFS配置
  6. Fiori extension hook和Hybris的template
  7. U盘文件系统无法识别,数据怎么恢复?
  8. 转载-----Java Longest Palindromic Substring(最长回文字符串)
  9. 分区起始位置参数溢出_IIS6.0缓冲区溢出漏洞深度分析(CVE-2017-7269)
  10. java 占位符_Java重要知识点
  11. php 当前时间 当前时间戳和数据库里取出的时间datetime格式进行比较大小
  12. try-catch(C# 参考)
  13. 【权值分块】bzoj1588 [HNOI2002]营业额统计
  14. 智能家居中控屏(二):产品设计
  15. 设计师培养设计思维的5个方法
  16. rabbitMQ 使用mqtt协议 tcp 和 ws
  17. Fortify SCA报告模板汇总
  18. Excel添加按键运行宏
  19. 读懂matlab代码,一个Matlab的寻峰程序没有看懂,不知大家能否帮助?
  20. 震惊!为了家人请不要这样对待自己的身体!

热门文章

  1. html 最小长度单位,html见长度单位尺寸单�?CSS布局HTML
  2. javascript创建DOM元素(标签script)并追加到title标签中
  3. docker之es+es-head+kibana+ik分词器安装
  4. 关于eclipase出现的problems during content assist报错问题
  5. Linux部署安装JDK
  6. CodeIgniter辅助函数
  7. HttpHandler和HttpModule 心得介绍
  8. 罗格斯的计算机科学,Rutgers的CS「罗格斯大学计算机科学系」
  9. vasp软件全名是什么_Materials Studio软件常见问题与解答
  10. 动态规划——使用最小花费爬楼梯(Leetcode 746)