下拉ScrollView伸缩头布局,实现ScrollView回弹效果

项目中用到了商品详情展示效果,所以立马想到借鉴天猫商品详情界面,看了天猫的详情页面想到了两套解决方案。1,使用LitView 添加header监听listView 的滑动然后根据listView 的滑动距离计算 header应该滑动的距离 和改变header的高度。2,使用ScrollView 代替1中的ListView 监听onTouch事件,动态改变header的高度,按照这个思路也可以实现ScrollView上下拉的回弹效果或者是上下拉刷新,思路都是一样。

由于项目的商品详情返回的数据 并不是一个集合 而且内容不统一所以使用方案2,下面先看看效果图还是图片有说服力。
下拉伸缩头布局
"上拉回弹"
下拉展开效果
这里的主要思路是:计算手指下拉滑动的距离然后设置给header布局,当手指松开时在把header的高度修改回原来的高度,这里用到了开源的动画库nineoldandroids(只需要在build引用compile files(‘libs/nineoldandroids-2.4.0.jar’)),在计算手指下拉滑动的距离时候需要判断ScrollView到达顶部的条件
上拉时候 判断ScrollView到达底部后然后的不走和上拉是一样的
下拉的时候 判断header的高度答到一个临界值的时候 打开header布局
下面是ScrollView 的完整代码

package com.app.test.myscrollview;

import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.ScrollView;

import com.nineoldandroids.animation.ValueAnimator;

/**
 * Created by Administrator on 2015/12/18.
 */
public class MyScrollView extends ScrollView {
    private ViewGroup innerLayout;//ScrololView里的布局
    private View headerView;// 头布局 必须在ScrollView里面
    private int originalHeight;//头布局原始高度
    private float downY;//手指按下的Y坐标
    private View emputyView;//空的布局  用于占位符
    private View footerView;//底部布局


    private boolean isOpen;
    private boolean isOpening;

    protected final static float OFFSET_RADIO = 1.8f; // 偏移量
    protected final static float OPEN_RADIO = 1.8f; // 打开比例

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

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

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


    //布局已经加载完成后调用  一些params参数在这里都能取到值了
    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        final int childCount = getChildCount();
        if (childCount == 1) {
            innerLayout = (ViewGroup) getChildAt(0);

            emputyView = new LinearLayout(getContext());
            ViewGroup.LayoutParams lp = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 0);
            emputyView.setLayoutParams(lp);

            footerView = new LinearLayout(getContext());
            footerView.setLayoutParams(lp);

            innerLayout.addView(emputyView, 0);
            innerLayout.addView(footerView, innerLayout.getChildCount());
        } else {
            throw new RuntimeException("ScrollView 只能有一个子布局");
        }
    }

    public void setOpen(boolean open) {
        isOpen = open;
    }

    public void setOpening(boolean opening) {
        isOpening = opening;
    }

    public void setHeaderView(View headerView) {
        if (headerView != null) {
            this.headerView = headerView;
            originalHeight = headerView.getLayoutParams().height;
        }
    }

    public void setOpenViewListener(OpenViewListener openViewListener) {
        this.openViewListener = openViewListener;
    }

    OpenViewListener openViewListener;

    public interface OpenViewListener {
        public void openVeiw(View headerVeiw);
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        final int action = ev.getAction();
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                downY = ev.getRawY();
                getParent().requestDisallowInterceptTouchEvent(true);
                break;
            case MotionEvent.ACTION_MOVE:
                float tempY = ev.getRawY();
                float delatY = tempY - downY;//手指竖直方向滑动的距离
                downY = tempY;
                float scrollY = getScrollY();//竖直方向 滚动的值
                float offset = innerLayout.getMeasuredHeight() - getHeight();//偏移量
                if (scrollY == 0 && delatY > 0) {//表示滑动到顶部了
                    int openOffset = (int) (delatY / OFFSET_RADIO);
                    if (headerView != null) {
                        int afterHeight = upDateViewHeight(headerView, openOffset);
                        if (afterHeight > originalHeight * OPEN_RADIO && openViewListener != null && !isOpen) {
                            //TODO  需要打开
                            isOpening = true;
                            openViewListener.openVeiw(headerView);
                        } else {
                            setViewHeight(headerView, afterHeight);
                        }
                    } else {
                        int afterHeight = upDateViewHeight(emputyView, openOffset);
                        setViewHeight(emputyView, afterHeight);
                    }
                }
                if (scrollY == offset && delatY < 0) {//滑动到底部了
                    int afterHeight = upDateViewHeight(footerView, (int) -delatY);
                    setViewHeight(footerView, afterHeight);
                }

                break;
            case MotionEvent.ACTION_CANCEL:
            case MotionEvent.ACTION_UP://手指弹开
                //手指弹开 让布局的高度 从现在的高度变成0 使用动画 也可以使用Scroller  使用动画简单
                if (headerView != null && headerView.getHeight() > originalHeight && !isOpening) {
                    closeView(headerView, headerView.getHeight(), originalHeight);
                } else if (emputyView.getHeight() > 0) {
                    closeView(emputyView, emputyView.getHeight(), 0);
                }

                if (footerView.getHeight() > 0) {
                    closeView(footerView, footerView.getHeight(), 0);
                }
                break;
        }
        return super.onTouchEvent(ev);
    }


    public void closeView(final View view, int fromHeight, final int toHeight) {
        ValueAnimator animator = ValueAnimator.ofInt(fromHeight, toHeight);
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {
                int height = (int) valueAnimator.getAnimatedValue();
                view.getLayoutParams().height = height;
                view.setLayoutParams(view.getLayoutParams());
                if (view == headerView && height == toHeight) {
                    isOpen = false;
                    isOpening = false;
                }
            }
        });
        animator.start();
        animator.setDuration(300);
    }

    public void openView(final View view, int fromHeight, final int toHeight) {
        ValueAnimator animator = ValueAnimator.ofInt(fromHeight, toHeight);
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {
                int height = (int) valueAnimator.getAnimatedValue();
                view.getLayoutParams().height = height;
                view.setLayoutParams(view.getLayoutParams());
                if (height == toHeight && view == headerView) {
                    isOpen = true;
                    isOpening = false;
                }
            }
        });
        animator.start();
        animator.setDuration(300);
    }


    /**
     * 改变 布局的高度
     *
     * @param view
     * @param upDateHeight 更新的高度
     * @return 改变后的高度
     */
    public int upDateViewHeight(View view, int upDateHeight) {
        int nowHeight = view.getLayoutParams().height;
        int afterHeight = nowHeight + upDateHeight;
        return afterHeight;
    }

    /**
     * 设置高度
     *
     * @param view
     * @param afterHeight
     */
    public void setViewHeight(View view, int afterHeight) {
        view.getLayoutParams().height = afterHeight;
        view.setLayoutParams(view.getLayoutParams());
    }
}

下面是布局文件

<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"
    android:orientation="vertical"
    tools:context=".MainActivity">


    <com.app.test.myscrollview.MyScrollView
        android:id="@+id/scrollView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:fillViewport="true"
        android:fitsSystemWindows="true">

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

            <com.app.test.demo.MyViewPager
                android:id="@+id/header"
                android:layout_width="match_parent"
                android:layout_height="190dp"
                >

            </com.app.test.demo.MyViewPager>

            <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:padding="20dp"
                android:text="@string/text"/>

            <ImageView
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:scaleType="centerCrop"
                android:src="@drawable/goods_sample"/>

            <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:padding="20dp"
                android:text="@string/text"/>
        </LinearLayout>
    </com.app.test.myscrollview.MyScrollView>

</FrameLayout>

在Activity中引用

package com.app.test;

import android.support.v4.view.PagerAdapter;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.DisplayMetrics;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;

import com.app.test.demo.GListView;
import com.app.test.demo.MyViewPager;
import com.app.test.myscrollview.BaseViewPgerAdapter;
import com.app.test.myscrollview.MyScrollView;

import java.util.ArrayList;
import java.util.List;

public class MainActivity extends AppCompatActivity {

    MyViewPager myViewPager;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        myViewPager = (MyViewPager) findViewById(R.id.header);
        List<String> list = new ArrayList<>();
        list.add("");
        list.add("");
        list.add("");
        list.add("");
        myViewPager.setAdapter(new BaseViewPgerAdapter<String>(list, R.layout.item_img) {
            @Override
            public void getView(View view, String item, int position) {
            }
        });
        final MyScrollView myScrollView = (MyScrollView) findViewById(R.id.scrollView);
        myScrollView.setHeaderView(myViewPager);
        myScrollView.setOpenViewListener(new MyScrollView.OpenViewListener() {
            @Override
            public void openVeiw(View headerVeiw) {
                myScrollView.openView(headerVeiw, headerVeiw.getHeight(), getScreenHeight());
            }
        });
    }

    /**
     * 得到屏幕高度
     *
     * @return 高度
     */
    public int getScreenHeight() {
        DisplayMetrics dm = new DisplayMetrics();
        getWindowManager().getDefaultDisplay().getMetrics(dm);
        int screenHeight = dm.heightPixels;
        return screenHeight;
    }

}

这个里的ViewPager是自定义ViewPager 因为 如果不对ViewPager做处理的话会产生滑动冲突导致ViewPager不能滑动的后果,对ViewPager的处理也比较简单主要是在dispatchTouchEvent事件里重新分发事件

 @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        int action = ev.getAction();
        if (action == MotionEvent.ACTION_DOWN) {
            downX = tempX = (int) ev.getX();
            downY = tempY = (int) ev.getY();
        } else if (action == MotionEvent.ACTION_UP) {
//            currentPage = this.getCurrentItem() + 1;
        } else if (action == MotionEvent.ACTION_MOVE) {
            int moveX = (int) ev.getX();
            int moveY = (int) ev.getY();
            int deltaX = tempX - moveX;
            int deltaY = tempY - moveY;
            tempX = moveX;
            tempY = moveY;
            if (Math.abs(deltaY) > Math.abs(deltaX)) {
                getParent().requestDisallowInterceptTouchEvent(false);
                return super.dispatchTouchEvent(ev);
            }
        }
        getParent().requestDisallowInterceptTouchEvent(true);
        return super.dispatchTouchEvent(ev);
    }

这是ViewPager的 dispatchTouchEvent方法 就是判断了手指滑动的水平距离和竖直距离

如果竖直方向的距离大于水平方向的距离则调用

getParent().requestDisallowInterceptTouchEvent(false);

这个方法的作用就是告诉父布局 可以拦截ViewPager的事件 这是ViewPager的ontouch不起作用

反之 当水平距离大于竖直距离时 则需要

getParent().requestDisallowInterceptTouchEvent(true);

告诉父容器不需要拦截事件 viewPager自己处理事件

在Activity中ViewPager设置的Adapter 是自己封装了一个PagerAdapter 这样写的好处就是省去了大量重复代码 其代码是:

package com.app.test.myscrollview;

import android.support.v4.view.PagerAdapter;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;

import java.util.List;

/**
 * Created by Administrator on 2015/12/18.
 */
public abstract class BaseViewPgerAdapter<T> extends PagerAdapter {

    List<T> datas;
    int layoutId;

    public BaseViewPgerAdapter(List<T> datas, int layoutId) {
        this.datas = datas;
        this.layoutId = layoutId;
    }

    @Override
    public int getCount() {
        return datas == null ? 0 : datas.size();
    }

    @Override
    public boolean isViewFromObject(View view, Object object) {
        return view == object;
    }

    @Override
    public void destroyItem(ViewGroup container, int position, Object object) {
        container.removeView((View) object);
    }

    public abstract void getView(View view, T item, int position);

    @Override
    public Object instantiateItem(ViewGroup container, int position) {
        View view = LayoutInflater.from(container.getContext()).inflate(layoutId, null);
        T item = datas.get(position);
        getView(view, item, position);
        container.addView(view);
        return view;
    }

    public ImageView setImageViewRec(View view, int imgId, int imageRec) {
        ImageView img = (ImageView) view.findViewById(imgId);
        img.setImageResource(imageRec);
        return img;
    }
}

好了到此结束了。 大致能够实现天猫商品详情的界面,当然这里还有需要可以改进的地方比如在上拉的时候 会有一丝丝的卡顿现象 暂时还没有找到解决办法 我想应该是因为手指轻微抖动导致footer的高度不断变化。
通过这个方法可实现很多中上下拉刷新的效果,已经个人中心界面类似天猫的个人中心界面,原理大致思路都是差不多的。
同时也求一款好的Gif截屏工具

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值