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