1. 程式人生 > >下拉ScrollView伸縮頭佈局,實現ScrollView回彈效果

下拉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截圖工具