Прикрепленный элемент прокрутки в нижней части экрана – Android

Я хотел реализовать липкий элемент scrollview, который находится в нижней части экрана. Ниже приведены несколько скриншотов, чтобы объяснить мой вопрос.

  1. На приведенном ниже экране показан фиксированный вид / компоновка в нижней части экрана, в котором говорится: «Сохранить и добавить в сумку»,

Макет / просмотр Застрял в нижней части страницы

  1. Когда пользователь прокручивает страницу вниз, макет / просмотр прокручивается со страницы. Как показано на следующем экране.

Прокрутка макета / вида со страницей

Вещи, которые я пробовал:

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

Я попытался перевернуть заголовок на дно, но не повезло!

Ваша помощь будет глубоко оценена.

Спасибо.

EDIT :

Ниже приводится прокрутка, которую я пытался сделать. Липкий вид все еще прилипает наверху, где он должен придерживаться дна.

public class StickyScrollView extends ScrollView { /** * Tag for views that should stick and have constant drawing. eg TextViews, ImageViews etc */ public static final String STICKY_TAG = "sticky"; /** * Flag for views that should stick and have non-constant drawing. eg 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; 

    Теперь вам нужно получить высоту по высоте экрана устройства, включая высоту строки состояния и высоты мягких кнопок, и вам нужно добавить высоту заголовка (если необходимо) в общую высоту.

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

    Для этого я создал библиотеку. Вы можете попробовать. Вот ссылка на lib https://github.com/amarjain07/StickyScrollView