下拉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 的完整代码
packagecom.app.test.myscrollview;
importandroid.content.Context;
importandroid.util.AttributeSet;
importandroid.view.MotionEvent;
importandroid.view.View;
importandroid.view.ViewGroup;
importandroid.widget.LinearLayout;
importandroid.widget.ScrollView;
importcom.nineoldandroids.animation.ValueAnimator;
/**
* Created by Administrator on 2015/12/18.
*/
public classMyScrollViewextendsScrollView {
privateViewGroupinnerLayout;//ScrololView里的布局
privateViewheaderView;//头布局 必须在ScrollView里面
private intoriginalHeight;//头布局原始高度
private floatdownY;//手指按下的Y坐标
privateViewemputyView;//空的布局 用于占位符
privateViewfooterView;//底部布局
private booleanisOpen;
private booleanisOpening;
protected final static floatOFFSET_RADIO=1.8f;//偏移量
protected final static floatOPEN_RADIO=1.8f;//打开比例
publicMyScrollView(Context context) {
this(context,null);
}
publicMyScrollView(Context context, AttributeSet attrs) {
this(context, attrs,0);
}
publicMyScrollView(Context context, AttributeSet attrs,intdefStyleAttr) {
super(context, attrs, defStyleAttr);
}
//布局已经加载完成后调用 一些params参数在这里都能取到值了
@Override
protected voidonFinishInflate() {
super.onFinishInflate();
final intchildCount = getChildCount();
if(childCount ==1) {
innerLayout= (ViewGroup) getChildAt(0);
emputyView=newLinearLayout(getContext());
ViewGroup.LayoutParams lp =newViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,0);
emputyView.setLayoutParams(lp);
footerView=newLinearLayout(getContext());
footerView.setLayoutParams(lp);
innerLayout.addView(emputyView,0);
innerLayout.addView(footerView,innerLayout.getChildCount());
}else{
throw newRuntimeException("ScrollView只能有一个子布局");
}
}
public voidsetOpen(booleanopen) {
isOpen= open;
}
public voidsetOpening(booleanopening) {
isOpening= opening;
}
public voidsetHeaderView(View headerView) {
if(headerView !=null) {
this.headerView= headerView;
originalHeight= headerView.getLayoutParams().height;
}
}
public voidsetOpenViewListener(OpenViewListener openViewListener) {
this.openViewListener= openViewListener;
}
OpenViewListeneropenViewListener;
public interfaceOpenViewListener {
public voidopenVeiw(View headerVeiw);
}
@Override
public booleanonTouchEvent(MotionEvent ev) {
final intaction = ev.getAction();
switch(action) {
caseMotionEvent.ACTION_DOWN:
downY= ev.getRawY();
getParent().requestDisallowInterceptTouchEvent(true);
break;
caseMotionEvent.ACTION_MOVE:
floattempY = ev.getRawY();
floatdelatY = tempY -downY;//手指竖直方向滑动的距离
downY= tempY;
floatscrollY = getScrollY();//竖直方向 滚动的值
floatoffset =innerLayout.getMeasuredHeight() - getHeight();//偏移量
if(scrollY ==0&& delatY >0) {//表示滑动到顶部了
intopenOffset = (int) (delatY /OFFSET_RADIO);
if(headerView!=null) {
intafterHeight = upDateViewHeight(headerView, openOffset);
if(afterHeight >originalHeight*OPEN_RADIO&&openViewListener!=null&& !isOpen) {
//TODO需要打开
isOpening=true;
openViewListener.openVeiw(headerView);
}else{
setViewHeight(headerView, afterHeight);
}
}else{
intafterHeight = upDateViewHeight(emputyView, openOffset);
setViewHeight(emputyView, afterHeight);
}
}
if(scrollY == offset && delatY <0) {//滑动到底部了
intafterHeight = upDateViewHeight(footerView, (int) -delatY);
setViewHeight(footerView, afterHeight);
}
break;
caseMotionEvent.ACTION_CANCEL:
caseMotionEvent.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 voidcloseView(finalView view,intfromHeight,final inttoHeight) {
ValueAnimator animator = ValueAnimator.ofInt(fromHeight, toHeight);
animator.addUpdateListener(newValueAnimator.AnimatorUpdateListener() {
@Override
public voidonAnimationUpdate(ValueAnimator valueAnimator) {
intheight = (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 voidopenView(finalView view,intfromHeight,final inttoHeight) {
ValueAnimator animator = ValueAnimator.ofInt(fromHeight, toHeight);
animator.addUpdateListener(newValueAnimator.AnimatorUpdateListener() {
@Override
public voidonAnimationUpdate(ValueAnimator valueAnimator) {
intheight = (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);
}
/**
*改变 布局的高度
*
*@paramview
*@paramupDateHeight更新的高度
*@return改变后的高度
*/
public intupDateViewHeight(View view,intupDateHeight) {
intnowHeight = view.getLayoutParams().height;
intafterHeight = nowHeight + upDateHeight;
returnafterHeight;
}
/**
*设置高度
*
*@paramview
*@paramafterHeight
*/
public voidsetViewHeight(View view,intafterHeight) {
view.getLayoutParams().height= afterHeight;
view.setLayoutParams(view.getLayoutParams());
}
}
下面是布局文件
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">
android:id="@+id/scrollView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true"
android:fitsSystemWindows="true">
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
android:id="@+id/header"
android:layout_width="match_parent"
android:layout_height="190dp"
>
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="20dp"
android:text="@string/text"/>
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
android:src="@drawable/goods_sample"/>
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="20dp"
android:text="@string/text"/>
在Activity中引用
packagecom.app.test;
importandroid.support.v4.view.PagerAdapter;
importandroid.support.v7.app.AppCompatActivity;
importandroid.os.Bundle;
importandroid.util.DisplayMetrics;
importandroid.view.LayoutInflater;
importandroid.view.View;
importandroid.view.ViewGroup;
importandroid.widget.BaseAdapter;
importandroid.widget.TextView;
importcom.app.test.demo.GListView;
importcom.app.test.demo.MyViewPager;
importcom.app.test.myscrollview.BaseViewPgerAdapter;
importcom.app.test.myscrollview.MyScrollView;
importjava.util.ArrayList;
importjava.util.List;
public classMainActivityextendsAppCompatActivity {
MyViewPagermyViewPager;
@Override
protected voidonCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
myViewPager= (MyViewPager) findViewById(R.id.header);
List list =newArrayList<>();
list.add("");
list.add("");
list.add("");
list.add("");
myViewPager.setAdapter(newBaseViewPgerAdapter(list, R.layout.item_img) {
@Override
public voidgetView(View view, String item,intposition) {
}
});
finalMyScrollView myScrollView = (MyScrollView) findViewById(R.id.scrollView);
myScrollView.setHeaderView(myViewPager);
myScrollView.setOpenViewListener(newMyScrollView.OpenViewListener() {
@Override
public voidopenVeiw(View headerVeiw) {
myScrollView.openView(headerVeiw, headerVeiw.getHeight(), getScreenHeight());
}
});
}
/**
*得到屏幕高度
*
*@return高度
*/
public intgetScreenHeight() {
DisplayMetrics dm =newDisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(dm);
intscreenHeight = dm.heightPixels;
returnscreenHeight;
}
}
这个里的ViewPager是自定义ViewPager 因为 如果不对ViewPager做处理的话会产生滑动冲突导致ViewPager不能滑动的后果,对ViewPager的处理也比较简单主要是在dispatchTouchEvent事件里重新分发事件
@Override
public booleandispatchTouchEvent(MotionEvent ev) {
intaction = 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) {
intmoveX = (int) ev.getX();
intmoveY = (int) ev.getY();
intdeltaX =tempX- moveX;
intdeltaY =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 这样写的好处就是省去了大量重复代码 其代码是:
packagecom.app.test.myscrollview;
importandroid.support.v4.view.PagerAdapter;
importandroid.view.LayoutInflater;
importandroid.view.View;
importandroid.view.ViewGroup;
importandroid.widget.ImageView;
importjava.util.List;
/**
* Created by Administrator on 2015/12/18.
*/
public abstract classBaseViewPgerAdapterextendsPagerAdapter {
Listdatas;
intlayoutId;
publicBaseViewPgerAdapter(List datas,intlayoutId) {
this.datas= datas;
this.layoutId= layoutId;
}
@Override
public intgetCount() {
returndatas==null?0:datas.size();
}
@Override
public booleanisViewFromObject(View view, Object object) {
returnview == object;
}
@Override
public voiddestroyItem(ViewGroup container,intposition, Object object) {
container.removeView((View) object);
}
public abstract voidgetView(View view,Titem,intposition);
@Override
publicObject instantiateItem(ViewGroup container,intposition) {
View view = LayoutInflater.from(container.getContext()).inflate(layoutId,null);
Titem =datas.get(position);
getView(view, item, position);
container.addView(view);
returnview;
}
publicImageView setImageViewRec(View view,intimgId,intimageRec) {
ImageView img = (ImageView) view.findViewById(imgId);
img.setImageResource(imageRec);
returnimg;
}
}
好了到此结束了。 大致能够实现天猫商品详情的界面,当然这里还有需要可以改进的地方
比如在上拉的时候 会有一丝丝的卡顿现象 暂时还没有找到解决办法 我想应该是因为手指轻微抖动导致footer的高度不断变化。
还有就是 简书怎么复制代码呀。。 这样复制的代码一点都不好呀,同时也求一款好的Gif截屏工具