转自夏神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>