底部的粘性 ScrollView 项目 - Android
Sticky ScrollView Item at the Bottom - Android
我想实现一个粘在屏幕底部的粘性滚动视图项目。下面是一些截图来解释我的问题。
- 下面的屏幕显示固定的 view/layout 在屏幕底部说 'Save to and Add to Bag'
- 当用户向下滚动页面时,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
我想实现一个粘在屏幕底部的粘性滚动视图项目。下面是一些截图来解释我的问题。
- 下面的屏幕显示固定的 view/layout 在屏幕底部说 'Save to and Add to Bag'
- 当用户向下滚动页面时,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