转自夏神blog:http://blog.csdn.net/xiaanming/article/details/17761431
一、大概了解下
美团网用的是Scrolling Tricks,但是只支持API11以上。夏神实现了的修改版可适用API11以下版本,说下夏神的思路:首先对ScrollView滚动监听,直接在OnScrollChanged()中就能获得滚动的Y值。
二、实现
第一步:界面引入自定义的ScrollView。注意需要两个地方,一个要固定的位置,另一个是实际显示的位置。第二步:添加自定义ScrollView的滚动监听事件。第三步:设置当主布局的状态或者控件的可见性发生改变时回调接口。
三、代码
1、自定义的ScrollView
package com.example.myscroll;
import android.widget.ScrollView;
import android.content.Context;
import android.util.AttributeSet;
/**
* @blog http://blog.csdn.net/xiaanming
*
* @author xiaanming
*
*/
public class MyScrollView extends ScrollView {
private OnScrollListener onScrollListener;
public MyScrollView(Context context) {
this(context, null);
}
public MyScrollView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public MyScrollView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
/**
* 设置滚动接口
* @param onScrollListener
*/
public void setOnScrollListener(OnScrollListener onScrollListener) {
this.onScrollListener = onScrollListener;
}
@Override
public int computeVerticalScrollRange() {
return super.computeVerticalScrollRange();
}
@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
super.onScrollChanged(l, t, oldl, oldt);
if(onScrollListener != null){
onScrollListener.onScroll(t);
}
}
/**
*
* 滚动的回调接口
*
* @author xiaanming
*
*/
public interface OnScrollListener{
/**
* 回调方法, 返回MyScrollView滑动的Y方向距离
* @param scrollY
* 、
*/
public void onScroll(int scrollY);
}
}
界面中引入:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/parent_layout"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<ImageView
android:id="@+id/imageView1"
android:layout_width="match_parent"
android:layout_height="45dip"
android:scaleType="centerCrop"
android:src="@drawable/navigation_bar" />
<com.example.myscroll.MyScrollView
android:id="@+id/scrollView"
android:layout_width="fill_parent"
android:layout_height="fill_parent" >
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" >
<ImageView
android:id="@+id/iamge"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/pic"
android:scaleType="centerCrop" />
<include
android:id="@+id/buy"
layout="@layout/buy_layout" />
<ImageView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/one"
android:scaleType="centerCrop" />
<ImageView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/one"
android:scaleType="centerCrop" />
<ImageView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/one"
android:scaleType="centerCrop" />
</LinearLayout>
<include
android:id="@+id/top_buy_layout"
layout="@layout/buy_layout" />
</FrameLayout>
</com.example.myscroll.MyScrollView>
</LinearLayout>
2、代码实现
public class MainActivity extends Activity implements OnScrollListener {
/**
* 自定义的MyScrollView
*/
private MyScrollView myScrollView;
/**
* 在MyScrollView里面的购买布局
*/
private LinearLayout mBuyLayout;
/**
* 位于顶部的购买布局
*/
private LinearLayout mTopBuyLayout;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
myScrollView = (MyScrollView) findViewById(R.id.scrollView);
mBuyLayout = (LinearLayout) findViewById(R.id.buy);
mTopBuyLayout = (LinearLayout) findViewById(R.id.top_buy_layout);
myScrollView.setOnScrollListener(this);
//当布局的状态或者控件的可见性发生改变回调的接口
findViewById(R.id.parent_layout).getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
//这一步很重要,使得上面的购买布局和下面的购买布局重合
onScroll(myScrollView.getScrollY());
}
});
}
@Override
public void onScroll(int scrollY) {
int mBuyLayout2ParentTop = Math.max(scrollY, mBuyLayout.getTop());
mTopBuyLayout.layout(0, mBuyLayout2ParentTop, mTopBuyLayout.getWidth(), mBuyLayout2ParentTop + mTopBuyLayout.getHeight());
}
}
四、含有多个购买布局的效果
即下一个购买布局将上一个购买布局顶上去,使用方法也简单:引入自定义ScrollView,不同于上面的;然后将需要设置的布局设置Tag为sticky。如:
<FrameLayout
android:layout_width="fill_parent"
android:layout_height="100dip"
android:background="#ff00ffff"
android:tag="sticky" >
<Button
android:id="@+id/button"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Button" />
</FrameLayout>
自定义ScrollView:
package com.example.stikyscrollview;
import java.util.LinkedList;
import java.util.List;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ScrollView;
public class StickyScrollView extends ScrollView {
private static final String STICKY = "sticky";
private View mCurrentStickyView ;
private Drawable mShadowDrawable;
private List<View> mStickyViews;
private int mStickyViewTopOffset;
private int defaultShadowHeight = 10;
private float density;
private boolean redirectTouchToStickyView;
/**
* 当点击Sticky的时候,实现某些背景的渐变
*/
private Runnable mInvalidataRunnable = new Runnable() {
@Override
public void run() {
if(mCurrentStickyView != null){
int left = mCurrentStickyView.getLeft();
int top = mCurrentStickyView.getTop();
int right = mCurrentStickyView.getRight();
int bottom = getScrollY() + (mCurrentStickyView.getHeight() + mStickyViewTopOffset);
invalidate(left, top, right, bottom);
}
postDelayed(this, 16);
}
};
public StickyScrollView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public StickyScrollView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
mShadowDrawable = context.getResources().getDrawable(R.drawable.sticky_shadow_default);
mStickyViews = new LinkedList<View>();
density = context.getResources().getDisplayMetrics().density;
}
/**
* 找到设置tag的View
* @param viewGroup
*/
private void findViewByStickyTag(ViewGroup viewGroup){
int childCount = ((ViewGroup)viewGroup).getChildCount();
for(int i=0; i<childCount; i++){
View child = viewGroup.getChildAt(i);
if(getStringTagForView(child).contains(STICKY)){
mStickyViews.add(child);
}
if(child instanceof ViewGroup){
findViewByStickyTag((ViewGroup)child);
}
}
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
if(changed){
findViewByStickyTag((ViewGroup)getChildAt(0));
}
showStickyView();
}
@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
super.onScrollChanged(l, t, oldl, oldt);
showStickyView();
}
/**
*
*/
private void showStickyView(){
View curStickyView = null;
View nextStickyView = null;
for(View v : mStickyViews){
int topOffset = v.getTop() - getScrollY();
if(topOffset <= 0){
if(curStickyView == null || topOffset > curStickyView.getTop() - getScrollY()){
curStickyView = v;
}
}else{
if(nextStickyView == null || topOffset < nextStickyView.getTop() - getScrollY()){
nextStickyView = v;
}
}
}
if(curStickyView != null){
mStickyViewTopOffset = nextStickyView == null ? 0 : Math.min(0, nextStickyView.getTop() - getScrollY() - curStickyView.getHeight());
mCurrentStickyView = curStickyView;
post(mInvalidataRunnable);
}else{
mCurrentStickyView = null;
removeCallbacks(mInvalidataRunnable);
}
}
private String getStringTagForView(View v){
Object tag = v.getTag();
return String.valueOf(tag);
}
/**
* 将sticky画出来
*/
@Override
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
if(mCurrentStickyView != null){
//先保存起来
canvas.save();
//将坐标原点移动到(0, getScrollY() + mStickyViewTopOffset)
canvas.translate(0, getScrollY() + mStickyViewTopOffset);
if(mShadowDrawable != null){
int left = 0;
int top = mCurrentStickyView.getHeight() + mStickyViewTopOffset;
int right = mCurrentStickyView.getWidth();
int bottom = top + (int)(density * defaultShadowHeight + 0.5f);
mShadowDrawable.setBounds(left, top, right, bottom);
mShadowDrawable.draw(canvas);
}
canvas.clipRect(0, mStickyViewTopOffset, mCurrentStickyView.getWidth(), mCurrentStickyView.getHeight());
mCurrentStickyView.draw(canvas);
//重置坐标原点参数
canvas.restore();
}
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if(ev.getAction() == MotionEvent.ACTION_DOWN){
redirectTouchToStickyView = true;
}
if(redirectTouchToStickyView){
redirectTouchToStickyView = mCurrentStickyView != null;
if(redirectTouchToStickyView){
redirectTouchToStickyView = ev.getY() <= (mCurrentStickyView
.getHeight() + mStickyViewTopOffset)
&& ev.getX() >= mCurrentStickyView.getLeft()
&& ev.getX() <= mCurrentStickyView.getRight();
}
}
if (redirectTouchToStickyView) {
ev.offsetLocation(0, -1 * ((getScrollY() + mStickyViewTopOffset) - mCurrentStickyView.getTop()));
}
return super.dispatchTouchEvent(ev);
}
private boolean hasNotDoneActionDown = true;
@Override
public boolean onTouchEvent(MotionEvent ev) {
if(redirectTouchToStickyView){
ev.offsetLocation(0, ((getScrollY() + mStickyViewTopOffset) - mCurrentStickyView.getTop()));
}
if(ev.getAction()==MotionEvent.ACTION_DOWN){
hasNotDoneActionDown = false;
}
if(hasNotDoneActionDown){
MotionEvent down = MotionEvent.obtain(ev);
down.setAction(MotionEvent.ACTION_DOWN);
super.onTouchEvent(down);
hasNotDoneActionDown = false;
}
if(ev.getAction()==MotionEvent.ACTION_UP || ev.getAction()==MotionEvent.ACTION_CANCEL){
hasNotDoneActionDown = true;
}
return super.onTouchEvent(ev);
}
}
自定义ScrollView用到的xml:
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" >
<gradient
android:angle="90"
android:centerColor="#22222222"
android:endColor="#AA222222"
android:startColor="#00222222" >
</gradient>
</shape>