底部的粘性 ScrollView 项目 - Android

Sticky ScrollView Item at the Bottom - Android

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

  1. 下面的屏幕显示固定的 view/layout 在屏幕底部说 'Save to and Add to Bag'

  1. 当用户向下滚动页面时,layout/view 随页面一起滚动。如下图所示。

我尝试过的事情:

1.StickyScrollViewItems 来自 emilsjolander: https://github.com/emilsjolander/StickyScrollViewItems/blob/master/library/src/com/emilsjolander/components/StickyScrollViewItems/StickyScrollView.java

我试图将页眉反转到底部,但没有成功!

非常感谢您的帮助。

谢谢。

编辑:

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

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);
    }
}
}

我有同样的要求来实现这种视图,并且我已经根据我的代码和逻辑找到了解决方案。所以你可以应用我与你分享的代码!

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

布局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;

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

 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);
                    }
                }
            });
        }
    });

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