底部的Sticky ScrollView项目 - Android

时间:2016-05-30 13:10:43

标签: android android-layout scrollview sticky-footer

我想实现粘贴在屏幕底部的滚动滚动视图项目。下面是几个截图来解释我的问题。

  1. 以下屏幕显示屏幕底部的固定视图/布局,说明“保存到并添加到行李”'
  2. Layout/View Stuck to the bottom of the page

    1. 当用户向下滚动页面时,布局/视图随页面滚动。如下面的屏幕所示。
    2. The layout/view scrolling with the page

      我尝试过的事情:

      1.由emilsjolander创建的StickyScrollViewItems: https://github.com/emilsjolander/StickyScrollViewItems/blob/master/library/src/com/emilsjolander/components/StickyScrollViewItems/StickyScrollView.java

      我试图将标题反转到底部,但没有运气!

      非常感谢您的帮助。

      谢谢。

      修改

      以下是我试图制作的scrollview。粘性视图仍然粘在顶部,它应该粘在底部。

      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, stickViewBottomOffset;
      private int stickyViewLeftOffset;
      private boolean redirectTouchesToStickyView;
      private boolean clippingToPadding;
      private boolean clipToPaddingHasBeenSet;
      
      private int mShadowHeight;
      private Drawable mShadowDrawable;
      
      private final Runnable invalidateRunnable = new Runnable() {
      
          @Override
          public void run() {
              if (currentlyStickingView != null) {
                  int l = getLeftForViewRelativeOnlyChild(currentlyStickingView);
                  int t = getBottomForViewRelativeOnlyChild(currentlyStickingView);
                  int r = getRightForViewRelativeOnlyChild(currentlyStickingView);
                  //int b = (int) (getScrollY() + (currentlyStickingView.getHeight() + stickyViewTopOffset));
                  int b = getBottomForViewRelativeOnlyChild(currentlyStickingView);
                  invalidate(l, t, r, b);
              }
              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) {
          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;
      }
      
      @Override
      protected void onLayout(boolean changed, int l, int t, int r, int b) {
          super.onLayout(changed, l, t, r, b);
          if (!clipToPaddingHasBeenSet) {
              clippingToPadding = true;
          }
          notifyHierarchyChanged();
      }
      
      @Override
      public void setClipToPadding(boolean clipToPadding) {
          super.setClipToPadding(clipToPadding);
          clippingToPadding = clipToPadding;
          clipToPaddingHasBeenSet = true;
      }
      
      @Override
      public void addView(View child) {
          super.addView(child);
          findStickyViews(child);
      }
      
      @Override
      public void addView(View child, int index) {
          super.addView(child, index);
          findStickyViews(child);
      }
      
      @Override
      public void addView(View child, int index, android.view.ViewGroup.LayoutParams params) {
          super.addView(child, index, params);
          findStickyViews(child);
      }
      
      @Override
      public void addView(View child, int width, int height) {
          super.addView(child, width, height);
          findStickyViews(child);
      }
      
      @Override
      public void addView(View child, android.view.ViewGroup.LayoutParams params) {
          super.addView(child, params);
          findStickyViews(child);
      }
      
      @Override
      protected void dispatchDraw(Canvas canvas) {
          super.dispatchDraw(canvas);
          if (currentlyStickingView != null) {
              canvas.save();
              //canvas.translate(getPaddingLeft() + stickyViewLeftOffset, getScrollY() + stickyViewTopOffset + (clippingToPadding ? getPaddingTop() : 0));
              canvas.translate(getPaddingLeft() + stickyViewLeftOffset, getScrollY() - stickViewBottomOffset + (clippingToPadding ? getPaddingBottom() : 0));
      
              //canvas.clipRect(0, (clippingToPadding ? -stickyViewTopOffset : 0),
              //getWidth() - stickyViewLeftOffset,
              //currentlyStickingView.getHeight() + mShadowHeight + 1);
      
              canvas.clipRect(0, currentlyStickingView.getHeight() - mShadowHeight, getWidth() - stickyViewLeftOffset, (clippingToPadding ? 0 : stickViewBottomOffset));
      
              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());
              canvas.clipRect(0, currentlyStickingView.getHeight(), getWidth(), (clippingToPadding ? 0 : stickViewBottomOffset));
              if (getStringTagForView(currentlyStickingView).contains(FLAG_HASTRANSPARANCY)) {
                  showView(currentlyStickingView);
                  currentlyStickingView.draw(canvas);
                  hideView(currentlyStickingView);
              } else {
                  currentlyStickingView.draw(canvas);
              }
              canvas.restore();
          }
      }
      
      @Override
      public 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.getY() <= (currentlyStickingView.getHeight() - stickViewBottomOffset) &&
                                  ev.getX() >= getLeftForViewRelativeOnlyChild(currentlyStickingView) &&
                                  ev.getX() <= getRightForViewRelativeOnlyChild(currentlyStickingView);
              }
          } else if (currentlyStickingView == null) {
              redirectTouchesToStickyView = false;
          }
          if (redirectTouchesToStickyView) {
              //ev.offsetLocation(0, -1 * ((getScrollY() + stickyViewTopOffset) - getTopForViewRelativeOnlyChild(currentlyStickingView)));
              ev.offsetLocation(0, 1 * ((getScrollY() + stickViewBottomOffset) - getBottomForViewRelativeOnlyChild(currentlyStickingView)));
          }
          return super.dispatchTouchEvent(ev);
      }
      
      private boolean hasNotDoneActionDown = true;
      
      @Override
      public boolean onTouchEvent(MotionEvent ev) {
          if (redirectTouchesToStickyView) {
              //ev.offsetLocation(0, ((getScrollY() + stickyViewTopOffset) - getTopForViewRelativeOnlyChild(currentlyStickingView)));
              ev.offsetLocation(0, ((getScrollY() - stickViewBottomOffset) - 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);
      }
      
      @Override
      protected void onScrollChanged(int l, int t, int oldl, int oldt) {
          super.onScrollChanged(l, t, oldl, oldt);
          doTheStickyThing();
      }
      
      private void doTheStickyThing() {
          View viewThatShouldStick = null;
          View approachingView = null;
          for (View v : stickyViews) {
              int viewTop = getTopForViewRelativeOnlyChild(v) - getScrollY() + (clippingToPadding ? 0 : getPaddingTop());
              int viewBottom = getBottomForViewRelativeOnlyChild(v) - getScrollY() + (clippingToPadding ? 0 : getPaddingBottom());
              Log.e("VIEW BOTTOM: ", "VIEW BOTTOM: " + viewBottom);
      
              //Log.e("VIEW TOP: ", "VIEW TOP: " + viewTop);
      
              //BOTTOM
              if (viewBottom >= 0) {
                  if (viewThatShouldStick == null || viewBottom > (getBottomForViewRelativeOnlyChild(viewThatShouldStick) - getScrollY() + (clippingToPadding ? 0 : getPaddingBottom()))) {
                      viewThatShouldStick = v;
                      Log.e("VIEW BOTTOM: ", "VIEW THAT SHOULD STICK: " + viewThatShouldStick);
                  }
              } else {
                  if (approachingView == null || viewBottom < (getBottomForViewRelativeOnlyChild(approachingView) - getScrollY() + (clippingToPadding ? 0 : getPaddingBottom()))) {
                      approachingView = v;
                      Log.e("VIEW BOTTOM: ", "APPROACHING VIEW: " + approachingView);
                  }
              }
      
              //            //TOP
              //            if (viewTop <= 0) {
              //                if (viewThatShouldStick == null || viewTop > (getTopForViewRelativeOnlyChild(viewThatShouldStick) - getScrollY() + (clippingToPadding ? 0 : getPaddingTop()))) {
              //                    viewThatShouldStick = v;
              //                }
              //            } else {
              //                if (approachingView == null || viewTop < (getTopForViewRelativeOnlyChild(approachingView) - getScrollY() + (clippingToPadding ? 0 : getPaddingTop()))) {
              //                    approachingView = v;
              //                }
              //            }
          }
      
          //BOTTOM
          if (viewThatShouldStick != null) {
              stickViewBottomOffset = approachingView == null ? 0 : Math.min(0, getBottomForViewRelativeOnlyChild(approachingView) - getScrollY() + (clippingToPadding ? 0 : getPaddingBottom() - viewThatShouldStick.getHeight()));
              if (viewThatShouldStick != currentlyStickingView) {
                  if (currentlyStickingView != null) {
                      stopStickingCurrentlyStickingView();
                      Log.e("BOTTOM UNSTUCK: ", "BOTTOM UNSTUCK: ");
                  }
                  stickyViewLeftOffset = getLeftForViewRelativeOnlyChild(viewThatShouldStick);
                  startStickingView(viewThatShouldStick);
                  Log.e("BOTTOM STUCK: ", "BOTTOM STUCK: " + viewThatShouldStick);
              }
          } else if (currentlyStickingView != null) {
              Log.e("BOTTOM UNSTUCK: ", "BOTTOM UNSTUCK: ");
              stopStickingCurrentlyStickingView();
          }
      
          //TOP
          //        if (viewThatShouldStick != null) {
          //            stickyViewTopOffset = approachingView == null ? 0 : Math.min(0, getTopForViewRelativeOnlyChild(approachingView) - getScrollY() + (clippingToPadding ? 0 : getPaddingTop()) - viewThatShouldStick.getHeight());
          ////            Log.e("VIEW TOP: ", "STICKY VIEW TOP OFFSET: " + stickyViewTopOffset);
          //            if (viewThatShouldStick != currentlyStickingView) {
          //                if (currentlyStickingView != null) {
          //                    stopStickingCurrentlyStickingView();
          //                }
          //                // only compute the left offset when we start sticking.
          //                stickyViewLeftOffset = getLeftForViewRelativeOnlyChild(viewThatShouldStick);
          //                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();
      }
      
      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);
          }
      }
      }
      

2 个答案:

答案 0 :(得分:6)

我有同样的要求来实现这种视图,我根据我的代码和逻辑找出了解决方案。所以你可以申请我与你分享的代码!

下面是我的项目图像,它是如何使用我的代码工作的。

enter image description here

布局XML

  <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@color/white"
        android:orientation="vertical">
     <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="56dp"
        android:background="@color/red">


// Fix header For Product Name

</RelativeLayout>
         <ScrollView
                android:id="@+id/scroll_main"
                android:layout_width="match_parent"
                android:layout_height="0dp"
                android:layout_weight="0.5">

        <LinearLayout
                        android:id="@+id/lin_upper"
                        android:layout_width="match_parent"
                        android:layout_height="match_parent"
                        android:orientation="vertical">

        /// For the Conternt that you want to put upper Side OF bottom View that is Fix>> In my case pager, PagerIndicator ,ProductName, Price ,Size And size list>>

        // Create this to get Upper Content height.. And Put your Content..


        </LinearLayout>
         <LinearLayout
                        android:id="@+id/llin_inner_button"
                        android:layout_width="match_parent"
                        android:layout_height="48dp"
                        android:orientation="horizontal"
                        android:visibility="visible">

        // Copy Bottom View that you want to Stick

        </LinearLayout>
         <TextView
                        android:id="@+id/txt_temp"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:layout_marginLeft="8dp"
                        android:layout_marginTop="8dp"
                        android:text="Lorazepam belongs to a group of drugs"/>

        </ScrollView>

        <LinearLayout
                android:id="@+id/llin_outer_button"
                android:layout_width="match_parent"
                android:layout_height="48dp"
                android:orientation="horizontal">

        // Your actual BottomView Here...

        </LinearLayout>

    </LinearLayout>

Java文件

声明你的变量

 private int lay_height = 0;
 int height = 0;

现在您需要根据设备屏幕高度获取高度,包括状态栏高度和软按钮高度,并且您需要将标题高度(如果需要)添加到总高度。

 public int getStatusBarHeight() {
    int result = 0;
    int resourceId = getResources().getIdentifier("status_bar_height", "dimen", "android");
    if (resourceId > 0) {
        result = getResources().getDimensionPixelSize(resourceId);
    }
    return result;
}

@SuppressLint("NewApi")
private int getSoftButtonsBarHeight() {
    // getRealMetrics is only available with API 17 and +
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
        DisplayMetrics metrics = new DisplayMetrics();
        getWindowManager().getDefaultDisplay().getMetrics(metrics);
        int usableHeight = metrics.heightPixels;
        getWindowManager().getDefaultDisplay().getRealMetrics(metrics);
        int realHeight = metrics.heightPixels;
        if (realHeight > usableHeight)
            return realHeight - usableHeight;
        else
            return 0;
    }
    return 0;
}

public int pxToDp(int px) {
    DisplayMetrics displayMetrics = this.getResources().getDisplayMetrics();
    int dp = Math.round(px / (displayMetrics.xdpi / DisplayMetrics.DENSITY_DEFAULT));
    return dp;
}

public static float dipToPixels(Context context, float dipValue) {
    DisplayMetrics metrics = context.getResources().getDisplayMetrics();
    return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dipValue, metrics);
}

计算高度

    Display display = getWindowManager().getDefaultDisplay();
    Point size = new Point();
    display.getSize(size);
    int width = size.x;


    if (getSoftButtonsBarHeight() == 0) {
        height = size.y - getStatusBarHeight() - getSoftButtonsBarHeight() - (int) dipToPixels(ProductDetailActivity.this, 104);

    } else {
        height = size.y - getStatusBarHeight() - getSoftButtonsBarHeight() - (int) dipToPixels(ProductDetailActivity.this, 56);
    }

    Log.v("height_sc", height + "" + "   " + getStatusBarHeight() + "  " + getSoftButtonsBarHeight() + "   " + size.y);

    ViewTreeObserver observer = lin_upper.getViewTreeObserver();
    observer.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {

        @Override
        public void onGlobalLayout() {
            // TODO Auto-generated method stub
            lay_height = lin_upper.getHeight();
            int headerLayoutWidth = lin_upper.getWidth();
            lin_upper.getViewTreeObserver().removeGlobalOnLayoutListener(
                    this);

            Log.v("height", lay_height + "");
        }
    });

现在,您需要在onScrollChanged()方法中实现滚动视图的功能。

 scroll_main.getViewTreeObserver().addOnScrollChangedListener(new ViewTreeObserver.OnScrollChangedListener() {

        @Override
        public void onScrollChanged() {
            new Handler().post(new Runnable() {
                @Override
                public void run() {
                    int scrollX = scroll_main.getScrollX(); //for horizontalScrollView
                    int scrollY = scroll_main.getScrollY(); //for verticalScrollView


                    int sc = scrollY + height;

                    Log.v("bottom", lay_height + "  Y=" + sc + "  " + scrollY + "   " + height);

                    if (sc >= lay_height) {

                        llin_outer_button.setVisibility(View.GONE);

                    } else {
                        llin_outer_button.setVisibility(View.VISIBLE);
                    }
                }
            });
        }
    });

答案 1 :(得分:4)

我已经实现了一个库来做到这一点。你可以尝试一下。这是lib的链接 https://github.com/amarjain07/StickyScrollView