package com.example.jj.myapplication;import java.util.ArrayList;import android.content.Context;import android.content.res.TypedArray;import android.graphics.Canvas;import android.graphics.drawable.Drawable;import android.os.Build;import android.util.AttributeSet;import android.view.MotionEvent;import android.view.View;import android.view.ViewGroup;import android.view.animation.AlphaAnimation;import android.widget.ScrollView;/** *  * @author Emil  - sjolander.emil@gmail.com * */public class StickyScrollView extends ScrollView {/** * Tag for views that should stick and have constant drawing. e.g. TextViews, ImageViews etc */public static final String STICKY_TAG = "sticky";/** * Flag for views that should stick and have non-constant drawing. e.g. Buttons, ProgressBars etc */public static final String FLAG_NONCONSTANT = "-nonconstant";/** * Flag for views that have aren't fully opaque */public static final String FLAG_HASTRANSPARANCY = "-hastransparancy";/** * Default height of the shadow peeking out below the stuck view. */private static final int DEFAULT_SHADOW_HEIGHT = 10; // dp;private ArrayList<View> stickyViews;private View currentlyStickingView;private float stickyViewTopOffset;private int stickyViewLeftOffset;private boolean redirectTouchesToStickyView;private boolean clippingToPadding;private boolean clipToPaddingHasBeenSet;private int mShadowHeight;private Drawable mShadowDrawable;private final Runnable invalidateRunnable = new Runnable() {@Overridepublic void run() {if(currentlyStickingView!=null){int l = getLeftForViewRelativeOnlyChild(currentlyStickingView);int t  = getBottomForViewRelativeOnlyChild(currentlyStickingView);int r = getRightForViewRelativeOnlyChild(currentlyStickingView);int b = (int) (getScrollY() + (currentlyStickingView.getHeight() + stickyViewTopOffset));invalidate(l,t,r,b);}//每隔16毫秒刷新一次postDelayed(this, 16);}};public StickyScrollView(Context context) {this(context, null);}public StickyScrollView(Context context, AttributeSet attrs) {this(context, attrs, android.R.attr.scrollViewStyle);}public StickyScrollView(Context context, AttributeSet attrs, int defStyle) {super(context, attrs, defStyle);setup();TypedArray a = context.obtainStyledAttributes(attrs,        R.styleable.StickyScrollView, defStyle, 0);    final float density = context.getResources().getDisplayMetrics().density;    int defaultShadowHeightInPix = (int) (DEFAULT_SHADOW_HEIGHT * density + 0.5f);    mShadowHeight = a.getDimensionPixelSize(        R.styleable.StickyScrollView_stuckShadowHeight,        defaultShadowHeightInPix);    int shadowDrawableRes = a.getResourceId(        R.styleable.StickyScrollView_stuckShadowDrawable, -1);    if (shadowDrawableRes != -1) {      mShadowDrawable = context.getResources().getDrawable(          shadowDrawableRes);    }    a.recycle();}/** * Sets the height of the shadow drawable in pixels. * * @param height */public void setShadowHeight(int height) {mShadowHeight = height;}public void setup(){stickyViews = new ArrayList<View>();}private int getLeftForViewRelativeOnlyChild(View v){int left = v.getLeft();while(v.getParent() != getChildAt(0)){v = (View) v.getParent();left += v.getLeft();}return left;}private int  getTopForViewRelativeOnlyChild(View v){//获取ScrollView里y轴的top位置,这里是循环获取的,否则只会获取当前这个布局下的y轴top位置int top = v.getTop();while(v.getParent() != getChildAt(0)){v = (View) v.getParent();top += v.getTop();}return top;}private int getRightForViewRelativeOnlyChild(View v){int right = v.getRight();while(v.getParent() != getChildAt(0)){v = (View) v.getParent();right += v.getRight();}return right;}private int getBottomForViewRelativeOnlyChild(View v){int bottom = v.getBottom();while(v.getParent() != getChildAt(0)){v = (View) v.getParent();bottom += v.getBottom();}return bottom;}@Overrideprotected void onLayout(boolean changed, int l, int t, int r, int b) {super.onLayout(changed, l, t, r, b);if(!clipToPaddingHasBeenSet){clippingToPadding = true;}notifyHierarchyChanged();}@Overridepublic void setClipToPadding(boolean clipToPadding) {super.setClipToPadding(clipToPadding);clippingToPadding  = clipToPadding;clipToPaddingHasBeenSet = true;}@Overridepublic void addView(View child) {super.addView(child);findStickyViews(child);}@Overridepublic void addView(View child, int index) {super.addView(child, index);findStickyViews(child);}@Overridepublic void addView(View child, int index, ViewGroup.LayoutParams params) {super.addView(child, index, params);findStickyViews(child);}@Overridepublic void addView(View child, int width, int height) {super.addView(child, width, height);findStickyViews(child);}@Overridepublic void addView(View child, ViewGroup.LayoutParams params) {super.addView(child, params);findStickyViews(child);}@Overrideprotected void dispatchDraw(Canvas canvas) {super.dispatchDraw(canvas);if(currentlyStickingView != null){canvas.save();//移动坐标的原点,以后画布局的位置都是基于该原点进行的。原点就是x、y的初始位置。canvas.translate(getPaddingLeft() + stickyViewLeftOffset, getScrollY() +   + (clippingToPadding ? getPaddingTop() : 0));//设置展示的范围canvas.clipRect(0, (clippingToPadding ? -stickyViewTopOffset : 0),          getWidth() - stickyViewLeftOffset,          currentlyStickingView.getHeight() + mShadowHeight + 1);      if (mShadowDrawable != null) {        int left = 0;        int right = currentlyStickingView.getWidth();        int top = currentlyStickingView.getHeight();        int bottom = currentlyStickingView.getHeight() + mShadowHeight;        mShadowDrawable.setBounds(left, top, right, bottom);        mShadowDrawable.draw(canvas);      }//设置展示的范围canvas.clipRect(0, (clippingToPadding ? -stickyViewTopOffset : 0), getWidth(), currentlyStickingView.getHeight());if(getStringTagForView(currentlyStickingView).contains(FLAG_HASTRANSPARANCY)){showView(currentlyStickingView);currentlyStickingView.draw(canvas);hideView(currentlyStickingView);}else{//画出该布局到ScrollView的顶部currentlyStickingView.draw(canvas);}canvas.restore();}}@Overridepublic boolean dispatchTouchEvent(MotionEvent ev) {if(ev.getAction()==MotionEvent.ACTION_DOWN){redirectTouchesToStickyView = true;}if(redirectTouchesToStickyView){redirectTouchesToStickyView = currentlyStickingView != null;if(redirectTouchesToStickyView){//判断用户触摸的位置是否在画出来的布局位置redirectTouchesToStickyView = ev.getY()<=(currentlyStickingView.getHeight()+stickyViewTopOffset) &&ev.getX() >= getLeftForViewRelativeOnlyChild(currentlyStickingView) && ev.getX() <= getRightForViewRelativeOnlyChild(currentlyStickingView);}}else if(currentlyStickingView == null){redirectTouchesToStickyView = false;}if(redirectTouchesToStickyView){//如果触摸的位置是画出来的位置,那么将这个触摸的事件转移到被画的布局真正位置。//注:我们画出来的布局其实是跟着ScrollView的Y轴滑动走的,它只是一个展示不能监听点击事件,所以这里要转移点击事件。ev.offsetLocation(0, -1*((getScrollY() + stickyViewTopOffset) - getTopForViewRelativeOnlyChild(currentlyStickingView)));}return super.dispatchTouchEvent(ev);}private boolean hasNotDoneActionDown = true;@Overridepublic boolean onTouchEvent(MotionEvent ev) {if(redirectTouchesToStickyView){ev.offsetLocation(0, ((getScrollY() + stickyViewTopOffset) - getTopForViewRelativeOnlyChild(currentlyStickingView)));} 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);}@Overrideprotected void onScrollChanged(int l, int t, int oldl, int oldt) {super.onScrollChanged(l, t, oldl, oldt);doTheStickyThing();}/** * 滑动的时候动态的判断哪些View需要展示在顶部 */private void doTheStickyThing() {View viewThatShouldStick = null;View approachingView = null;for(View v : stickyViews){//当前的滚动Y值是否已经超过了标志sticky view的显示位置int viewTop = getTopForViewRelativeOnlyChild(v) - getScrollY() + (clippingToPadding ? 0 : getPaddingTop());if(viewTop<=0){//超过了就在这里赋值下,准备做view置顶显示if(viewThatShouldStick==null || viewTop>(getTopForViewRelativeOnlyChild(viewThatShouldStick) - getScrollY() + (clippingToPadding ? 0 : getPaddingTop()))){viewThatShouldStick = v;}}else{//下一个即将显示的viewif(approachingView == null || viewTop<(getTopForViewRelativeOnlyChild(approachingView) - getScrollY() + (clippingToPadding ? 0 : getPaddingTop()))){approachingView = v;}}}if(viewThatShouldStick!=null){stickyViewTopOffset = approachingView == null ? 0 : Math.min(0, getTopForViewRelativeOnlyChild(approachingView) - getScrollY()  + (clippingToPadding ? 0 : getPaddingTop()) - viewThatShouldStick.getHeight());//如果当前sticky view已经发生改变了,停止之前的view刷新if(viewThatShouldStick != currentlyStickingView){if(currentlyStickingView!=null){stopStickingCurrentlyStickingView();}// only compute the left offset when we start sticking.stickyViewLeftOffset = getLeftForViewRelativeOnlyChild(viewThatShouldStick);//开始对这个view做置顶显示startStickingView(viewThatShouldStick);}}else if(currentlyStickingView!=null){stopStickingCurrentlyStickingView();}}private void startStickingView(View viewThatShouldStick) {currentlyStickingView = viewThatShouldStick;if(getStringTagForView(currentlyStickingView).contains(FLAG_HASTRANSPARANCY)){hideView(currentlyStickingView);}if(((String)currentlyStickingView.getTag()).contains(FLAG_NONCONSTANT)){post(invalidateRunnable);}}private void stopStickingCurrentlyStickingView() {if(getStringTagForView(currentlyStickingView).contains(FLAG_HASTRANSPARANCY)){showView(currentlyStickingView);}currentlyStickingView = null;removeCallbacks(invalidateRunnable);}/** * Notify that the sticky attribute has been added or removed from one or more views in the View hierarchy */public void notifyStickyAttributeChanged(){notifyHierarchyChanged();}private void notifyHierarchyChanged(){if(currentlyStickingView!=null){stopStickingCurrentlyStickingView();}stickyViews.clear();findStickyViews(getChildAt(0));doTheStickyThing();invalidate();}/** * 找到所有需要粘贴在顶部的view放在view集合中 */private void findStickyViews(View v) {if(v instanceof ViewGroup){ViewGroup vg = (ViewGroup)v;for(int i = 0 ; i<vg.getChildCount() ; i++){String tag = getStringTagForView(vg.getChildAt(i));if(tag!=null && tag.contains(STICKY_TAG)){stickyViews.add(vg.getChildAt(i));}else if(vg.getChildAt(i) instanceof ViewGroup){findStickyViews(vg.getChildAt(i));}}}else{String tag = (String) v.getTag();if(tag!=null && tag.contains(STICKY_TAG)){stickyViews.add(v);}}}private String getStringTagForView(View v){Object tagObject = v.getTag();return String.valueOf(tagObject);}private void hideView(View v) {if(Build.VERSION.SDK_INT>=11){v.setAlpha(0);}else{AlphaAnimation anim = new AlphaAnimation(1, 0);anim.setDuration(0);anim.setFillAfter(true);v.startAnimation(anim);}}private void showView(View v) {if(Build.VERSION.SDK_INT>=11){v.setAlpha(1);}else{AlphaAnimation anim = new AlphaAnimation(0, 1);anim.setDuration(0);anim.setFillAfter(true);v.startAnimation(anim);}}}

更多相关文章

  1. 自定义布局-自定义RelativeLayout
  2. Android布局颜色对应值
  3. DrawerLayout中加入多个View
  4. Android(安卓)线性布局 LinearLayout
  5. ConstraintLayout约束布局属性
  6. Android——基于ConstraintLayout实现的可拖拽位置控件
  7. Android之自定义最简单的竖向引导页
  8. Android(安卓)利用ViewPager+GridView,仿美团首页导航栏分类布局
  9. Android(安卓)Studio安装配置详细步骤(超详细)

随机推荐

  1. Android关于在Canvas类里的绘制线程问题
  2. Ubuntu 安装 Android(安卓)Studio 全过程
  3. Android存储登陆信息
  4. Android中模拟HOME键功能
  5. ViewBinding与RecycleView(一)
  6. Android移动应用知识点总汇①
  7. RecyclerView嵌套ScrollView,滑动卡顿解
  8. Java/Android(安卓)实现简单的HTTP服务器
  9. Android(安卓)使用ActivityOptions实现Ac
  10. Android(安卓)Retrofit2网路编程实现方法