使用ScrollView制作Title渐变,配合RecyclerView展示列表

最近UI 设计了一个小动画,作为Android 小程序员,自然不能示弱,在不屑的努力下,终于做出了一个Demo的雏形。

完成效果如下

这里写图片描述

准备工作

首先添加要使用的控件

dependencies {
    compile 'com.android.support:appcompat-v7:25.3.1'
    compile 'com.android.support:recyclerview-v7:25.3.1'
    compile 'com.android.support:design:25.3.1'
}

新建一个自定义 ScrollView 类

public class DetailScrollView extends ScrollView
{
    public DetailScrollView(Context context)
    {
        this(context, null);
    }

    public DetailScrollView(Context context, AttributeSet attrs)
    {
        super(context, attrs);
    }
}

布局文件大致写一下

<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/white">

    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="12dp"
        android:src="@android:drawable/ic_menu_sort_by_size" />

    <ImageView
        android:id="@+id/iv_detail_collect"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="right"
        android:layout_margin="12dp"
        android:src="@android:drawable/btn_star_big_off" />

    <!--下面开始就是猪脚了-->
    <next.vr_view2.view.DetailScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical">

            <RelativeLayout
                android:layout_width="200dp"
                android:layout_height="234dp"
                android:layout_gravity="center_horizontal">

                <ImageView
                    android:id="@+id/iv_logo"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_centerHorizontal="true"
                    android:src="@mipmap/ic_launcher_round" />
            </RelativeLayout>

            <android.support.design.widget.TabLayout
                android:id="@+id/tl_detail"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                app:tabIndicatorColor="#ee1717"
                app:tabSelectedTextColor="#ee1717"
                app:tabTextColor="#515151">

            </android.support.design.widget.TabLayout>


            <FrameLayout
                android:id="@+id/fl_fragment_container"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:background="#f4f4f4" />
        </LinearLayout>
    </next.vr_view2.view.DetailScrollView>
</FrameLayout>

Activity 也简单的添两句

public class DetailActivity extends AppCompatActivity implements TabLayout.OnTabSelectedListener
{

    TabLayout mTabLayout;

    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.f_shop_detail_view);
        mTabLayout = (TabLayout) findViewById(R.id.tl_detail);
        mTabLayout.addTab(mTabLayout.newTab().setText("Tab1"));
        mTabLayout.addTab(mTabLayout.newTab().setText("Tab2"));
        mTabLayout.addOnTabSelectedListener(this);
    }

    @Override
    public void onTabSelected(TabLayout.Tab tab)
    {

    }
}

对View 进行初始化

回到 咋们的自定义View 中来
首先通过onSizeChanged 获取这个View 可用的总高度

    //这个View 会使用的高度
    private int thisViewHeight;

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh)
    {
        super.onSizeChanged(w, h, oldw, oldh);
        //首先在 获取这个View 的高度
        thisViewHeight = h;
    }

然后获取 需要动态改变的ImageView ,这里我偷了一个懒- -

    private ImageView mLogoIv;

    @Override
    protected void onAttachedToWindow()
    {
        super.onAttachedToWindow();
        mLogoIv = (ImageView) findViewById(R.id.iv_logo);
    }

然后在 onLayout 对布局信息进行初始化

    //是否对布局进行初始
    private boolean isInitLayout = true;
    //滑到顶部超出屏幕高度
    private int mScrollTopBeyondScreenHeight;
    //将View 高度均分 中的一份
    private float ah;

    //对 logo ImageView 初始值进行存储
    private int logoStartMarginTop;
    private int slogoStartWidth;

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b)
    {
        super.onLayout(changed, l, t, r, b);
        if (isInitLayout)
        {
            //其实就是布局文件中 我们写的那个LinearLayout
            ViewGroup group = (ViewGroup) getChildAt(0);
            //RelativeLayout
            ViewGroup v1 = (ViewGroup) group.getChildAt(0);
            //android.support.design.widget.TabLayout
            View v2 = group.getChildAt(1);
            //FrameLayout
            View v3 = group.getChildAt(2);
            // TabLayout 是不会 改变了
            // 减去它之后就是我们要使用的,可以改变的长度
            int mh = thisViewHeight - v2.getHeight();

            //假如我们把总长分为 13份,头部(RelativeLayout)占据 4份
            //那尾部(FrameLayout) 就是 13 - 4 份
            int maxT = 13;
            int topT = 4;

            //计算一份的值
            ah = mh * 1f / 10;

            ViewGroup.LayoutParams v1lp = v1.getLayoutParams();
            //+1 是因为  float 转换为 int 会有误差 ,使用+1 来弥补误差
            v1lp.height = (int) (ah * topT) + 1;
            v1.setLayoutParams(v1lp);

            ViewGroup.LayoutParams v3lp = v3.getLayoutParams();
            v3lp.height = (int) (ah * (maxT - topT)) + 1;
            v3.setLayoutParams(v3lp);

            //对要动态改变的ImageView 设置大小,上边距 等。这里的信息可以根据自己的爱好修改。
            RelativeLayout.LayoutParams logoLp = (RelativeLayout.LayoutParams) mLogoIv.getLayoutParams();
            //对 logo 初始值进行存储
            logoLp.topMargin = logoStartMarginTop = (int) (ah / 4);
            logoLp.width = slogoStartWidth = v1lp.height / 4;
            logoLp.height = slogoStartWidth;
            mLogoIv.setLayoutParams(logoLp);

            //计算出滑动顶部超出屏幕的高度,这个值很重要
            mScrollTopBeyondScreenHeight = v1lp.height + v3lp.height - mh;
            //完成初始化
            isInitLayout = false;
        }
    }

我知道这段代码又臭又长,但是一定要冷静看,代码本身并没有难度,这样才能进行下一步~

效果1

可以看到大体模样是出来了~

动态改变

计算 需要使用 到 onScrollChanged(int l, int t, int oldl, int oldt) 方法
其中 t 代表是 滑动后 超出屏幕的多少值
语言表达有障碍,所以直接上代码和效果吧

    @Override
    protected void onScrollChanged(int l, int t, int oldl, int oldt)
    {
        super.onScrollChanged(l, t, oldl, oldt);


        float f = 1 - (t * 1f / mScrollTopBeyondScreenHeight);

        Log.i("next", "渐变比例-->" + f);

        RelativeLayout.LayoutParams logoLp = (RelativeLayout.LayoutParams) mLogoIv.getLayoutParams();
        logoLp.topMargin = logoStartMarginTop + t;
        logoLp.width = (int) (slogoStartWidth / 2 + slogoStartWidth / 2 * f);
        logoLp.height = logoLp.width;
        mLogoIv.setLayoutParams(logoLp);

        //TODO 如果还有其他View 需要动态改变可以根据 f 动态进行改变
    }

效果2

到这里基本效果就算完成了,然后剩下的就是一些意想不到的 意外

当嵌套RecyclerView后的滑动事件处理

为 Activity中添加 RecyclerView ,仅贴出添加代码



    private final int LAYOUT_ID = R.id.fl_fragment_container;
    private TestFragment tab1Fragment;
    private TestFragment tab2Fragment;


    private void initData()
    {
        tab1Fragment = new TestFragment();
        tab2Fragment = new TestFragment();

        List<String> data1 = new ArrayList<>();
        List<String> data2 = new ArrayList<>();
        for (int i = 'a'; i < 'z'; i++)
        {
            data1.add("数据1:" + (char) i);
            data2.add((char) i + "--》我的是数据2");
        }
        tab1Fragment.setData(data1);
        tab2Fragment.setData(data2);
        getSupportFragmentManager().beginTransaction()//
                .add(LAYOUT_ID, tab1Fragment, "tab1")//
                .add(LAYOUT_ID, tab2Fragment, "tab2")//
                .hide(tab2Fragment)//
                .commit();
    }

    @Override
    public void onTabSelected(TabLayout.Tab tab)
    {
        FragmentTransaction tf = getSupportFragmentManager().beginTransaction();
        switch (tab.getPosition())
        {
            case 0:
                tf.show(tab1Fragment).hide(tab2Fragment);
                break;
            default:
            case 1:
                tf.show(tab2Fragment).hide(tab1Fragment);
                break;
        }
        tf.commit();
    }

至于 TestFragment 就是一个简单的RecyclerView的Fragment

public class TestFragment extends Fragment
{

    RecyclerView mRecyclerView;
    List<String> data = new ArrayList<>();
    TestAdapter mAdapter;

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState)
    {
        return inflater.inflate(R.layout.recycler_view, container, false);
    }

    public void setData(List<String> data)
    {
        if (data != null)
            this.data = data;
        if (mAdapter != null)
            mAdapter.notifyDataSetChanged();
    }

    @Override
    public void onViewCreated(View view, @Nullable Bundle savedInstanceState)
    {
        mRecyclerView = (RecyclerView) view.findViewById(R.id.recycler_view);
        mRecyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
        mRecyclerView.setAdapter(new TestAdapter());
    }

    private class TestAdapter extends RecyclerView.Adapter<TestViewHolder>
    {
        @Override
        public TestViewHolder onCreateViewHolder(ViewGroup parent, int viewType)
        {
            return new TestViewHolder(getActivity().getLayoutInflater().inflate(android.R.layout.simple_list_item_1, parent, false));
        }

        @Override
        public void onBindViewHolder(TestViewHolder holder, int position)
        {
            holder.mTextView.setText(data.get(position));
        }

        @Override
        public int getItemCount()
        {
            return data.size();
        }
    }

    private class TestViewHolder extends RecyclerView.ViewHolder
    {
        TextView mTextView;

        public TestViewHolder(View itemView)
        {
            super(itemView);
            mTextView = (TextView) itemView.findViewById(android.R.id.text1);
        }
    }

}

然后问题就出来了,先上图
这里写图片描述

问题1 ,RecyclerView 会抢占用ScrollView的 向上滑动事件

问题2,点击Tab进行切换的时候,ScrollView 会滑动到顶部

那么一个个问题来解决

RecyclerView 会抢占用ScrollView的 向上滑动事件

这个问题只需要屏蔽事件即可,当Scroll View 没有滑动到顶部,我们拦截子View 向上滑动事件即可
还是直接看代码吧

    //是否滑动到顶部
    private boolean isScrollTop;
    @Override
    protected void onScrollChanged(int l, int t, int oldl, int oldt)
    {
    isScrollTop = t == mScrollTopBeyondScreenHeight;
    //... 此处省略之前代码
    }

    //按下的点
    private float lastY;

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev)
    {
        boolean isScrollUp = false;
        switch (ev.getAction())
        {
            case MotionEvent.ACTION_DOWN:
                lastY = ev.getY();
                break;
            case MotionEvent.ACTION_MOVE:
                //y 变小说明是在向上滑
                isScrollUp = lastY - ev.getY() > 0;
                break;
            case MotionEvent.ACTION_UP:
                lastY = 0;
                break;
        }

        //如果是向上滑动,并且 没有滑动到顶部,对事件进行拦截
        if (isScrollUp && !isScrollTop) return true;

        return super.onInterceptTouchEvent(ev);
    }

问题1

可以看见向上滑动事件受到了制裁

点击Tab进行切换的时候,ScrollView 会滑动到顶部

这个问题我并没有绝对的把握能够处理好,还处于研究中。
我发现每次切换Tab的时候,scrollTo 方法会被执行,所以我取了一个巧,当 滑动 y == 超出屏幕最高度 时就屏蔽事件

@Override
    public void scrollTo(int x, int y)
    {
        if (y == mScrollTopBeyondScreenHeight) return;
        //Log.i("next", "scrollTo x ->" + x + "  y ->" + y);
        super.scrollTo(x, y);
    }

效果
问题2

问题2 的解决方案还有待考证。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值