您的位置:首页 > 其它

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

2015-12-22 10:48 489 查看
项目中用到了商品详情展示效果,所以立马想到借鉴天猫商品详情界面,看了天猫的详情页面想到了两套解决方案。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截屏工具
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: