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

效果
在这里插入图片描述

一 布局分析:

分成三部分
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;

    @Override
    protected 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 {//角色定位 抽象观察者 类比Observer

    void notifyDataSetChanged();
}

View

class CustomFilterView extends RelativeLayout implements View.OnClickListener {//角色定位 CustomFilterView相当于ListView

    private 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的个数增加 逻辑也变得复杂,层次不够清晰 容易出bug
        inflate(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的setAdapter
        if (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);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        //设置阴影区域为整个view的1/5
        LayoutParams mShadowViewLayoutParams = (LayoutParams) mShadowView.getLayoutParams();
        mShadowViewLayoutParams.height = MeasureSpec.getSize(heightMeasureSpec) / 5;
        mShadowView.setLayoutParams(mShadowViewLayoutParams);
    }

    @Override
    public 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() {
            @Override
            public void onAnimationStart(Animator animation) {

            }

            @Override
            public void onAnimationEnd(Animator animation) {
                mContainerContent.setVisibility(GONE);
                mShadowView.setVisibility(GONE);
                isAnimating = false;
            }

            @Override
            public void onAnimationCancel(Animator animation) {

            }

            @Override
            public 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() {
            @Override
            public void onAnimationStart(Animator animation) {

            }

            @Override
            public void onAnimationEnd(Animator animation) {
                isAnimating = false;
            }

            @Override
            public void onAnimationCancel(Animator animation) {

            }

            @Override
            public void onAnimationRepeat(Animator animation) {

            }
        });
    }

    //角色定位 具体观察者 类比ListView中的AdapterDataSetObserver
    class AdapterObserver implements Observer {
        @Override
        public void notifyDataSetChanged() {
            closeContent();
        }
    }
}

Adapter接口

public interface ViewAdapter {

    //角色定位 抽象的被观察者 参考ListView的Adapter.java
    void registerObserver(Observer observer);

    void unregisterObserver(Observer observer);

    void notifyContentItemClick(View view);


    // 获取总共有几个tab页
    abstract int getCount();

    // 获取当前tab的TabView
    abstract View getTabView(int position);

    // 获取当前tab的菜单内容
    abstract View getContentView(int position);
}

Adapter实现类

class ViewBaseAdapter implements ViewAdapter {//角色定位 具体的被观察者 类比ListView的BaseAdapter
    private Context mContext;
    String[] tabTexts = {"美食", "生活", "鬼畜", "动漫区", "其他"};
    protected final ArrayList<Observer> mObservers = new ArrayList<>();

    @Override
    public void registerObserver(Observer observer) {
        mObservers.add(observer);
    }

    @Override
    public void unregisterObserver(Observer observer) {
        mObservers.remove(observer);
    }

    @Override
    public void notifyContentItemClick(View view) {
        for (Observer observer : mObservers) {
            observer.notifyDataSetChanged();
        }
    }


    public ViewBaseAdapter(Context context) {
        this.mContext = context;
    }

    @Override
    public int getCount() {
        return tabTexts.length;
    }

    @Override
    public 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;
    }

    @Override
    public 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">


    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:text="Hello World!" />

    <com.example.customfilterview.CustomFilterView
        android: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">

    <RelativeLayout
        android: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" />

    <LinearLayout
        android:background="#fff"
        android:id="@+id/container_tab"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal" />

    <View
        android: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

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值