I'm currently using a BluringView XML Object as gotten from the BlurringView.java file (https://github.com/500px/500px-android-blur). It's basically just a custom view that blurs a sibling view that's beneath it. The problem I'm having is that I can't populate the BlurringView with other objects. Here's the code:
package com.fivehundredpx.android.blur;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.support.v8.renderscript.Allocation;
import android.support.v8.renderscript.Element;
import android.support.v8.renderscript.RenderScript;
import android.support.v8.renderscript.ScriptIntrinsicBlur;
import android.util.AttributeSet;
import android.view.View;
/**
* A custom view for presenting a dynamically blurred version of another view's content.
* <p/>
* Use {#link #setBlurredView(android.view.View)} to set up the reference to the view to be blurred.
* After that, call {#link #invalidate()} to trigger blurring whenever necessary.
*/
public class BlurringView extends View {
public BlurringView(Context context) {
this(context, null);
}
public BlurringView(Context context, AttributeSet attrs) {
super(context, attrs);
final Resources res = getResources();
final int defaultBlurRadius = res.getInteger(R.integer.default_blur_radius);
final int defaultDownsampleFactor = res.getInteger(R.integer.default_downsample_factor);
final int defaultOverlayColor = res.getColor(R.color.default_overlay_color);
initializeRenderScript(context);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.PxBlurringView);
setBlurRadius(a.getInt(R.styleable.PxBlurringView_blurRadius, defaultBlurRadius));
setDownsampleFactor(a.getInt(R.styleable.PxBlurringView_downsampleFactor,
defaultDownsampleFactor));
setOverlayColor(a.getColor(R.styleable.PxBlurringView_overlayColor, defaultOverlayColor));
a.recycle();
}
public void setBlurredView(View blurredView) {
mBlurredView = blurredView;
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (mBlurredView != null) {
if (prepare()) {
// If the background of the blurred view is a color drawable, we use it to clear
// the blurring canvas, which ensures that edges of the child views are blurred
// as well; otherwise we clear the blurring canvas with a transparent color.
if (mBlurredView.getBackground() != null && mBlurredView.getBackground() instanceof ColorDrawable){
mBitmapToBlur.eraseColor(((ColorDrawable) mBlurredView.getBackground()).getColor());
}else {
mBitmapToBlur.eraseColor(Color.TRANSPARENT);
}
mBlurredView.draw(mBlurringCanvas);
blur();
canvas.save();
canvas.translate(mBlurredView.getX() - getX(), mBlurredView.getY() - getY());
canvas.scale(mDownsampleFactor, mDownsampleFactor);
canvas.drawBitmap(mBlurredBitmap, 0, 0, null);
canvas.restore();
}
canvas.drawColor(mOverlayColor);
}
}
public void setBlurRadius(int radius) {
mBlurScript.setRadius(radius);
}
public void setDownsampleFactor(int factor) {
if (factor <= 0) {
throw new IllegalArgumentException("Downsample factor must be greater than 0.");
}
if (mDownsampleFactor != factor) {
mDownsampleFactor = factor;
mDownsampleFactorChanged = true;
}
}
public void setOverlayColor(int color) {
mOverlayColor = color;
}
private void initializeRenderScript(Context context) {
mRenderScript = RenderScript.create(context);
mBlurScript = ScriptIntrinsicBlur.create(mRenderScript, Element.U8_4(mRenderScript));
}
protected boolean prepare() {
final int width = mBlurredView.getWidth();
final int height = mBlurredView.getHeight();
if (mBlurringCanvas == null || mDownsampleFactorChanged
|| mBlurredViewWidth != width || mBlurredViewHeight != height) {
mDownsampleFactorChanged = false;
mBlurredViewWidth = width;
mBlurredViewHeight = height;
int scaledWidth = width / mDownsampleFactor;
int scaledHeight = height / mDownsampleFactor;
// The following manipulation is to avoid some RenderScript artifacts at the edge.
scaledWidth = scaledWidth - scaledWidth % 4 + 4;
scaledHeight = scaledHeight - scaledHeight % 4 + 4;
if (mBlurredBitmap == null
|| mBlurredBitmap.getWidth() != scaledWidth
|| mBlurredBitmap.getHeight() != scaledHeight) {
mBitmapToBlur = Bitmap.createBitmap(scaledWidth, scaledHeight,
Bitmap.Config.ARGB_8888);
if (mBitmapToBlur == null) {
return false;
}
mBlurredBitmap = Bitmap.createBitmap(scaledWidth, scaledHeight,
Bitmap.Config.ARGB_8888);
if (mBlurredBitmap == null) {
return false;
}
}
mBlurringCanvas = new Canvas(mBitmapToBlur);
mBlurringCanvas.scale(1f / mDownsampleFactor, 1f / mDownsampleFactor);
mBlurInput = Allocation.createFromBitmap(mRenderScript, mBitmapToBlur,
Allocation.MipmapControl.MIPMAP_NONE, Allocation.USAGE_SCRIPT);
mBlurOutput = Allocation.createTyped(mRenderScript, mBlurInput.getType());
}
return true;
}
protected void blur() {
mBlurInput.copyFrom(mBitmapToBlur);
mBlurScript.setInput(mBlurInput);
mBlurScript.forEach(mBlurOutput);
mBlurOutput.copyTo(mBlurredBitmap);
}
#Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
if (mRenderScript != null){
mRenderScript.destroy();
}
}
private int mDownsampleFactor;
private int mOverlayColor;
private View mBlurredView;
private int mBlurredViewWidth, mBlurredViewHeight;
private boolean mDownsampleFactorChanged;
private Bitmap mBitmapToBlur, mBlurredBitmap;
private Canvas mBlurringCanvas;
private RenderScript mRenderScript;
private ScriptIntrinsicBlur mBlurScript;
private Allocation mBlurInput, mBlurOutput;
}
Is there any way to modify the BlurringView.java class so the BlurringView xml object can be used like a RelativeView or something similar? I want to be able to have the BlurringView be the parent to other objects.
I need this, because I'm using the BlurringView together with a SlidingUpPanelLayout (https://github.com/umano/AndroidSlidingUpPanel) and the SlidingUpPanelLayout only allows two children for it to function. Thus I can't just overlay items over the BlurringView. I need the BlurringView to be their parent.
Please let me know if you need clarification.
-R
Blockquote Did you try to do an extract method of your constructor, implement the same methon in each of the three default constructors and extend from RelativeLayout?
The RelativeLayout actually have a onDraw method, maybe isn't being called because you didn't invoke the method setWillNotDraw(false), try using that method to trigger the onDraw or use an invalidate() at the end of the constructors.
#astinx answered this question.
All I had to do was change the BlurringView class to extend a RelativeLayout instead of a View. I then called the setWillNotDraw(false) method as well as the invalidate() method in the constructor and now I'm able to populate the BlurringView with children! Magic.
-R
Related
I have an activity that is using "adjustPan" as its resize config, and I need to calculate keyboard height without using "adjustResize" because I need to keep some views as fullscreen (which means they should stay where they are, the keyboard should hide them), and position a view right above the keyboard. Our application has a message button and I open the keyboard via the button click. When it happens, I use an OnGlobalLayoutListener and use "getWindowVisibleDisplayFrame" method to get the keyboard's height. Here is some code for it:
private void message()
{
InputMethodManager methodManager =
(InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
if (methodManager != null && !isKeyboardOpen)
{
methodManager.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0);
if (bottomCoordinate == 0)
{
RelativeLayout layout = findViewById(getFullScreenContainerId());
layout.getViewTreeObserver().addOnGlobalLayoutListener(
new ViewTreeObserver.OnGlobalLayoutListener()
{
#Override
public void onGlobalLayout()
{
layout.getViewTreeObserver().removeOnGlobalLayoutListener(this);
Rect r = new Rect();
layout.getWindowVisibleDisplayFrame(r);
bottomCoordinate = r.bottom - r.top;
translateMessageView(bottomCoordinate);
}
});
}
else
translateMessageView(bottomCoordinate);
isKeyboardOpen = true;
}
}
"translateMessageView" basically sets the view's Y coordinate to "bottomCoordinate - view.getHeight()". This works good up until the autocorrect part of the soft keyboard applications becomes visible. Apparently "getWindowVisibleDisplayFrame" method doesn't seem to add autocorrect part of the view or "onGlobalLayout" method is not called when soft keyboard's autocorrect part shows up, and the positioned view stays under it which makes it half-visible. I need to be able to adjust its position again, so what should I do? What is the correct approach for this? Any suggestion is valuable, thank you.
Here is the way I detect the Keyboard height in any activity, this also caters for the notch/cutout height if there is one.
KeyboardHeightProvider.java
import android.content.Context;
import android.graphics.Rect;
import android.graphics.drawable.ColorDrawable;
import android.util.DisplayMetrics;
import android.view.DisplayCutout;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.WindowInsets;
import android.view.WindowManager;
import android.widget.LinearLayout;
import android.widget.PopupWindow;
import androidx.core.view.DisplayCutoutCompat;
import androidx.core.view.OnApplyWindowInsetsListener;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
public class KeyboardHeightProvider extends PopupWindow implements OnApplyWindowInsetsListener {
private View decorView;
private DisplayMetrics metrics;
private LinearLayout popupView;
private ViewTreeObserver.OnGlobalLayoutListener globalLayoutListener;
private Rect insets = new Rect(0, 0, 0, 0);
public KeyboardHeightProvider(Context context, WindowManager windowManager, View decorView, KeyboardHeightListener listener) {
super(context);
this.decorView = decorView;
metrics = new DisplayMetrics();
windowManager.getDefaultDisplay().getMetrics(metrics);
popupView = new LinearLayout(context);
popupView.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
globalLayoutListener = () -> {
windowManager.getDefaultDisplay().getMetrics(metrics);
int keyboardHeight = getKeyboardHeight();
boolean screenLandscape = metrics.widthPixels > metrics.heightPixels;
if (listener != null) {
listener.onKeyboardHeightChanged(keyboardHeight, screenLandscape);
}
};
setContentView(popupView);
setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE | WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED);
setWidth(0);
setHeight(ViewGroup.LayoutParams.MATCH_PARENT);
setBackgroundDrawable(new ColorDrawable(0));
ViewCompat.setOnApplyWindowInsetsListener(popupView, this);
}
public void start() {
popupView.getViewTreeObserver().addOnGlobalLayoutListener(globalLayoutListener);
decorView.post(() -> showAtLocation(decorView, Gravity.NO_GRAVITY, 0, 0));
}
#Override
public void dismiss() {
popupView.getViewTreeObserver().removeOnGlobalLayoutListener(globalLayoutListener);
super.dismiss();
}
#Override
public WindowInsetsCompat onApplyWindowInsets(View v, WindowInsetsCompat insets) {
DisplayCutoutCompat cutoutCompat = insets.getDisplayCutout();
if (cutoutCompat != null) {
this.insets.set(cutoutCompat.getSafeInsetLeft(), cutoutCompat.getSafeInsetTop(), cutoutCompat.getSafeInsetRight(), cutoutCompat.getSafeInsetBottom());
} else {
this.insets.set(insets.getSystemWindowInsetLeft(), insets.getSystemWindowInsetTop(), insets.getSystemWindowInsetRight(), insets.getSystemWindowInsetBottom());
}
if (decorView != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
WindowInsets rootWindowInsets = decorView.getRootWindowInsets();
if (rootWindowInsets != null) {
DisplayCutout displayCutout = rootWindowInsets.getDisplayCutout();
if (displayCutout != null) {
this.insets.set(displayCutout.getSafeInsetLeft(), displayCutout.getSafeInsetTop(), displayCutout.getSafeInsetRight(), displayCutout.getSafeInsetBottom());
}
}
}
return insets;
}
public int getKeyboardHeight() {
Rect rect = new Rect();
popupView.getWindowVisibleDisplayFrame(rect);
int keyboardHeight = metrics.heightPixels - (rect.bottom - rect.top) - (insets.bottom - insets.top);
int resourceID = context.getResources().getIdentifier("status_bar_height", "dimen", "android");
if (resourceID > 0) {
keyboardHeight -= context.getResources().getDimensionPixelSize(resourceID);
}
if (keyboardHeight < 100) {
keyboardHeight = 0;
}
return keyboardHeight;
}
public interface KeyboardHeightListener {
void onKeyboardHeightChanged(int height, boolean isLandscape);
}
}
On your activity:
android:windowSoftInputMode can be anything, mine is stateHidden|adjustNothing
implement KeyboardHeightProvider.KeyboardHeightListener
add a global variable:
private KeyboardHeightProvider keyboardHeightProvider;
in onCreate add the line:
keyboardHeightProvider = new KeyboardHeightProvider(this, getWindowManager(), getWindow().getDecorView(), this);
in onResume add the line:
keyboardHeightProvider.start();
in onPause add the line:
keyboardHeightProvider.dismiss();
in onKeyboardHeightChanged
#Override
public void onKeyboardHeightChanged(int height, boolean isLandscape) {
//This will be called anytime the keyboard height has changed.
boolean keyboardOpen = height > 0;
//do what you want with Keyboard Height
}
(Not original Answer)
Rect r = new Rect();
View rootview = this.getWindow().getDecorView(); // this = activity
rootview.getWindowVisibleDisplayFrame(r);
Result of this is the amount of space your application uses on screen (works even when activity is not resized). Obviously remaining screen space will be used by the keyboard ( if its visible)
Found id up here: https://github.com/freshplanet/ANE-KeyboardSize/blob/master/android/src/com/freshplanet/ane/KeyboardSize/getKeyboardY.java
You can visit the Original answer
Getting the dimensions of the soft keyboard
Typically how sticky headers work is that there's some sort of scrollable data that is divided into sections, each with their own header, and as you scroll down, the headers of subsequent sections replace the header at the top of the ScrollView.
What I need is to have additional sticky headers within each respective section. For example, if header1 is stuck to the top, its first section's header --header1a-- is stuck underneath it, but when we get to section 1b, 1b's header will replace 1a's, but leaving header1 stuck in the same place; and when we finally get to section 2, header2 will replace the currently stuck headers from the previous section -- header1 and header1b.
Here is a ScrollView implementation that implements sticky headers in a one-dimensional fashion:
https://github.com/emilsjolander/StickyScrollViewItems
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.AlphaAnimation;
import java.util.ArrayList;
/**
*
* #author Emil Sj�lander - sjolander.emil#gmail.com
*
*/
public class StickyScrollView extends ScrollView {
/**
* Tag for views that should stick and have constant drawing. e.g. TextViews, ImageViews etc
*/
public static final String STICKY_TAG = "sticky";
/**
* Flag for views that should stick and have non-constant drawing. e.g. Buttons, ProgressBars etc
*/
public static final String FLAG_NONCONSTANT = "-nonconstant";
/**
* Flag for views that have aren't fully opaque
*/
public static final String FLAG_HASTRANSPARANCY = "-hastransparancy";
/**
* Default height of the shadow peeking out below the stuck view.
*/
private static final int DEFAULT_SHADOW_HEIGHT = 10; // dp;
private ArrayList<View> mStickyViews;
private View mCurrentlyStickingView;
private float mStickyViewTopOffset;
private int mStickyViewLeftOffset;
private boolean mRedirectTouchesToStickyView;
private boolean mClippingToPadding;
private boolean mClipToPaddingHasBeenSet;
private int mShadowHeight;
private Drawable mShadowDrawable;
private final Runnable mInvalidateRunnable = new Runnable() {
#Override
public void run() {
if(mCurrentlyStickingView !=null){
int l = getLeftForViewRelativeOnlyChild(mCurrentlyStickingView);
int t = getBottomForViewRelativeOnlyChild(mCurrentlyStickingView);
int r = getRightForViewRelativeOnlyChild(mCurrentlyStickingView);
int b = (int) (getScrollY() + (mCurrentlyStickingView.getHeight() + mStickyViewTopOffset));
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(){
mStickyViews = 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(!mClipToPaddingHasBeenSet){
mClippingToPadding = true;
}
notifyHierarchyChanged();
}
#Override
public void setClipToPadding(boolean clipToPadding) {
super.setClipToPadding(clipToPadding);
mClippingToPadding = clipToPadding;
mClipToPaddingHasBeenSet = 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(mCurrentlyStickingView != null){
canvas.save();
canvas.translate(getPaddingLeft() + mStickyViewLeftOffset, getScrollY() + mStickyViewTopOffset + (mClippingToPadding ? getPaddingTop() : 0));
canvas.clipRect(0, (mClippingToPadding ? -mStickyViewTopOffset : 0), getWidth() - mStickyViewLeftOffset,mCurrentlyStickingView.getHeight() + mShadowHeight + 1);
if (mShadowDrawable != null) {
int left = 0;
int right = mCurrentlyStickingView.getWidth();
int top = mCurrentlyStickingView.getHeight();
int bottom = mCurrentlyStickingView.getHeight() + mShadowHeight;
mShadowDrawable.setBounds(left, top, right, bottom);
mShadowDrawable.draw(canvas);
}
canvas.clipRect(0, (mClippingToPadding ? -mStickyViewTopOffset : 0), getWidth(), mCurrentlyStickingView.getHeight());
if(getStringTagForView(mCurrentlyStickingView).contains(FLAG_HASTRANSPARANCY)){
showView(mCurrentlyStickingView);
mCurrentlyStickingView.draw(canvas);
hideView(mCurrentlyStickingView);
}else{
mCurrentlyStickingView.draw(canvas);
}
canvas.restore();
}
}
#Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if(ev.getAction()==MotionEvent.ACTION_DOWN){
mRedirectTouchesToStickyView = true;
}
if(mRedirectTouchesToStickyView){
mRedirectTouchesToStickyView = mCurrentlyStickingView != null;
if(mRedirectTouchesToStickyView){
mRedirectTouchesToStickyView =
ev.getY()<=(mCurrentlyStickingView.getHeight()+ mStickyViewTopOffset) &&
ev.getX() >= getLeftForViewRelativeOnlyChild(mCurrentlyStickingView) &&
ev.getX() <= getRightForViewRelativeOnlyChild(mCurrentlyStickingView);
}
}else if(mCurrentlyStickingView == null){
mRedirectTouchesToStickyView = false;
}
if(mRedirectTouchesToStickyView){
ev.offsetLocation(0, -1*((getScrollY() + mStickyViewTopOffset) - getTopForViewRelativeOnlyChild(mCurrentlyStickingView)));
}
return super.dispatchTouchEvent(ev);
}
private boolean hasNotDoneActionDown = true;
#Override
public boolean onTouchEvent(MotionEvent ev) {
if(mRedirectTouchesToStickyView){
ev.offsetLocation(0, ((getScrollY() + mStickyViewTopOffset) - getTopForViewRelativeOnlyChild(mCurrentlyStickingView)));
}
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 approachingStickyView = null;
for(View v : mStickyViews){
int viewTop = getTopForViewRelativeOnlyChild(v) - getScrollY() + (mClippingToPadding ? 0 : getPaddingTop());
if(viewTop<=0){
if(viewThatShouldStick==null || viewTop>(getTopForViewRelativeOnlyChild(viewThatShouldStick) - getScrollY() + (mClippingToPadding ? 0 : getPaddingTop()))){
viewThatShouldStick = v;
}
}else{
if(approachingStickyView == null || viewTop<(getTopForViewRelativeOnlyChild(approachingStickyView) - getScrollY() + (mClippingToPadding ? 0 : getPaddingTop()))){
approachingStickyView = v;
}
}
}
if(viewThatShouldStick!=null){
mStickyViewTopOffset = approachingStickyView == null ? 0 : Math.min(0, getTopForViewRelativeOnlyChild(approachingStickyView) - getScrollY() + (mClippingToPadding ? 0 : getPaddingTop()) - viewThatShouldStick.getHeight());
if(viewThatShouldStick != mCurrentlyStickingView){
if(mCurrentlyStickingView !=null){
stopStickingCurrentlyStickingView();
}
// only compute the left offset when we start sticking.
mStickyViewLeftOffset = getLeftForViewRelativeOnlyChild(viewThatShouldStick);
startStickingView(viewThatShouldStick);
}
}else if(mCurrentlyStickingView !=null){
stopStickingCurrentlyStickingView();
}
}
private void startStickingView(View viewThatShouldStick) {
mCurrentlyStickingView = viewThatShouldStick;
if(getStringTagForView(mCurrentlyStickingView).contains(FLAG_HASTRANSPARANCY)){
hideView(mCurrentlyStickingView);
}
if(((String) mCurrentlyStickingView.getTag()).contains(FLAG_NONCONSTANT)){
post(mInvalidateRunnable);
}
}
private void stopStickingCurrentlyStickingView() {
if(getStringTagForView(mCurrentlyStickingView).contains(FLAG_HASTRANSPARANCY)){
showView(mCurrentlyStickingView);
}
mCurrentlyStickingView = null;
removeCallbacks(mInvalidateRunnable);
}
/**
* 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(mCurrentlyStickingView !=null){
stopStickingCurrentlyStickingView();
}
mStickyViews.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)){
mStickyViews.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)){
mStickyViews.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);
}
}
}
What I'm trying to do is to adapt it to suit my needs, but I've tried poking around in this implementation to see how it does what it does and I cannot figure out how it gets the view to get stuck to the top of the ScrollView. Does anyone have any idea how this works?
Edit:
Here is the layout that I want to apply this concept too:
*Keep in mind that the Headers (Headers 1 & 2) are custom ViewGroups that contains the Sub-Headers (Header 1a, 1b, 2a); which are also custom ViewGroups that contain custom views which are the Items.
The StickyScrollView you are using is just saving a tag to whether it should be sticky or not and if not which child of scrollview is it's header, and according to that it is maintaining it as a first child view.
If you want to use this StickyScrollView only you have to modify it and maintain one more tag as sub-header.
I will suggest rather using this ScrollView, you can use this ListView. It is very easy to implement and it works.
You can use header-decor for your requirement. Internally its using RecyclerView, so it is advisable to use it. Check Double Header section in below gif.
Hope this will help you.
This isn't rocket science. There's two key parts to understanding this.
First is in the method doTheStickyThing. This figures out what goes where.
The initial step is figuring out which header to stick. Once you scroll down, you have views both above and below the top of the scroll view. You want to stick the bottom-most header that is still above the top of the scroll view. So you see a lot of expressions like this:
getTopForViewRelativeOnlyChild(viewThatShouldStick) - getScrollY() + (clippingToPadding ? 0 : getPaddingTop()))
That resulting value is just the offset of the top of the view from the top of the scroll view. If the header is above the top of the scroll view, the value is negative. So it turns out you want the header that has the greatest offset value that is still less than or equal to zero. The winning view gets assigned to viewThatShouldStick.
Now that you have a sticking header, you want to know which following header might start pushing that one out of the way when scrolling. That gets assigned to approachingView
If the approaching view is pushing the header out of the way, you have to offset the top of the header. That value is assigned to stickyViewTopOffset
The second key part is drawing the header. That's done in dispatchDraw.
Here's the trick to making the view look "stuck": The normal rendering logic would like to put that header at a certain place based on its current bounds. We can just move the canvas (translate) underneath that header so that it draws at the top of the scroll view instead of wherever it would normally draw. Then we tell the view to draw itself. This happens after all the list item views have been already been drawn, so the header appears to float on top of the list items.
When we move the canvas around, we also have to take into account the case where another approaching header is starting to push this one out of the way. The clipping handles some corner cases concerning how things should look when paddings are involved.
I started working on modifying the code to do what you wanted, but things got complicated fast.
Instead of tracking two headers, you need to track three headers: header, subheader, and approaching header. Now you have to handle the top offset of the subheader along with the top offset of the header. And then you have two scenarios: First is that the approaching header is a main header. This is going to modify both top offsets. But when the approaching header is a subheader, only the top offset of the pinned subheader is affected, and the main header offset stays the same.
I can get this, but I'm short on time right now. I'll finish off the code and post it if I can find the time.
I'm trying to achieve this effect that can be seen above on StickyListHeaders's sample app:
Basically I need to show a single, static, fixed header view on top of a ListView but bellow its scrollbar. I don't need anything related to sections or alphabetical indexing or anything like that.
I'm unable to figure out how to do this based on the source code of StickyListHeaders. I tried subclassing ListView and overriding dispatchDraw() like this:
protected void dispatchDraw(Canvas canvas)
{
View view = LayoutInflater.from(getContext()).inflate(R.layout.header, this, false);
drawChild(canvas, view, getDrawingTime());
super.dispatchDraw(canvas);
}
But it doesn't work, no header is drawn.
Answering my own question. This ListView subclass is able to do what I wanted. The first element of the list can become fixed calling showFixedHeader():
import android.content.Context;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.view.View;
import android.widget.ListAdapter;
import android.widget.ListView;
public class FixedHeaderListView extends ListView
{
private View fixedHeader = null;
private boolean fixedHeaderLayoutDone = false;
private boolean showFixedHeader = true;
#SuppressWarnings("unused")
public FixedHeaderListView(Context context)
{
super(context);
}
#SuppressWarnings("unused")
public FixedHeaderListView(Context context, AttributeSet attrs)
{
super(context, attrs);
}
#SuppressWarnings("unused")
public FixedHeaderListView(Context context, AttributeSet attrs, int defStyle)
{
super(context, attrs, defStyle);
}
public void showFixedHeader(boolean show)
{
this.showFixedHeader = show;
requestLayout(); // Will cause layoutChildren() and dispatchDraw() to be called
}
#Override
protected void layoutChildren()
{
super.layoutChildren();
if (!fixedHeaderLayoutDone)
{
ListAdapter adapter = getAdapter();
if (adapter != null && adapter.getCount() > 0)
{
// Layout the first item in the adapter's data set as the fixed header
fixedHeader = adapter.getView(0, null, this);
if (fixedHeader != null)
{
// Measure and layout
LayoutParams layoutParams = (LayoutParams)fixedHeader.getLayoutParams();
if (layoutParams == null)
{
layoutParams = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
}
int heightMode = MeasureSpec.getMode(layoutParams.height);
if (heightMode == MeasureSpec.UNSPECIFIED)
{
heightMode = MeasureSpec.EXACTLY;
}
int heightSize = MeasureSpec.getSize(layoutParams.height);
int maxHeight = getHeight() - getListPaddingTop() - getListPaddingBottom();
if (heightSize > maxHeight)
{
heightSize = maxHeight;
}
int widthSpec = MeasureSpec.makeMeasureSpec(getWidth() - getListPaddingLeft() - getListPaddingRight(), MeasureSpec.EXACTLY);
int heightSpec = MeasureSpec.makeMeasureSpec(heightSize, heightMode);
fixedHeader.measure(widthSpec, heightSpec);
fixedHeader.layout(0, 0, fixedHeader.getMeasuredWidth(), fixedHeader.getMeasuredHeight());
// Flag as layout done
fixedHeaderLayoutDone = true;
}
}
}
}
#Override #SuppressWarnings("NullableProblems")
protected void dispatchDraw(Canvas canvas)
{
super.dispatchDraw(canvas);
if (fixedHeader != null && showFixedHeader)
{
drawChild(canvas, fixedHeader, getDrawingTime());
}
}
}
It's not heavily tested, but it's a good starting point.
I am trying to draw a ball to my screen using 3 classes. I have read a little about this and I found a code snippet that works using the 3 classes on one page, Playing with graphics in Android
I altered the code so that I have a ball that is moving and shifts direction when hitting the wall like the picture below (this is using the code in the link).
Now I like to separate the classes into 3 different pages for not making everything so crowded, everything is set up the same way.
Here are the 3 classes I have.
BallActivity.java
Ball.java
BallThread.java
package com.brick.breaker;
import android.app.Activity;
import android.os.Bundle;
import android.view.Window;
import android.view.WindowManager;
public class BallActivity extends Activity {
private Ball ball;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,WindowManager.LayoutParams.FLAG_FULLSCREEN);
ball = new Ball(this);
setContentView(ball);
}
#Override
protected void onPause() {
super.onPause();
setContentView(null);
ball = null;
finish();
}
}
package com.brick.breaker;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
public class Ball extends SurfaceView implements SurfaceHolder.Callback {
private BallThread ballThread = null;
private Bitmap bitmap;
private float x, y;
private float vx, vy;
public Ball(Context context) {
super(context);
bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.ball);
x = 50.0f;
y = 50.0f;
vx = 10.0f;
vy = 10.0f;
getHolder().addCallback(this);
ballThread = new BallThread(getHolder(), this);
}
protected void onDraw(Canvas canvas) {
update(canvas);
canvas.drawBitmap(bitmap, x, y, null);
}
public void update(Canvas canvas) {
checkCollisions(canvas);
x += vx;
y += vy;
}
public void checkCollisions(Canvas canvas) {
if(x - vx < 0) {
vx = Math.abs(vx);
} else if(x + vx > canvas.getWidth() - getBitmapWidth()) {
vx = -Math.abs(vx);
}
if(y - vy < 0) {
vy = Math.abs(vy);
} else if(y + vy > canvas.getHeight() - getBitmapHeight()) {
vy = -Math.abs(vy);
}
}
public int getBitmapWidth() {
if(bitmap != null) {
return bitmap.getWidth();
} else {
return 0;
}
}
public int getBitmapHeight() {
if(bitmap != null) {
return bitmap.getHeight();
} else {
return 0;
}
}
public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height) {
}
public void surfaceCreated(SurfaceHolder holder) {
ballThread.setRunnable(true);
ballThread.start();
}
public void surfaceDestroyed(SurfaceHolder holder) {
boolean retry = true;
ballThread.setRunnable(false);
while(retry) {
try {
ballThread.join();
retry = false;
} catch(InterruptedException ie) {
//Try again and again and again
}
break;
}
ballThread = null;
}
}
package com.brick.breaker;
import android.graphics.Canvas;
import android.view.SurfaceHolder;
public class BallThread extends Thread {
private SurfaceHolder sh;
private Ball ball;
private Canvas canvas;
private boolean run = false;
public BallThread(SurfaceHolder _holder,Ball _ball) {
sh = _holder;
ball = _ball;
}
public void setRunnable(boolean _run) {
run = _run;
}
public void run() {
while(run) {
canvas = null;
try {
canvas = sh.lockCanvas(null);
synchronized(sh) {
ball.onDraw(canvas);
}
} finally {
if(canvas != null) {
sh.unlockCanvasAndPost(canvas);
}
}
}
}
public Canvas getCanvas() {
if(canvas != null) {
return canvas;
} else {
return null;
}
}
}
Here is a picture that shows the outcome of these classes.
I've tried to figure this out but since I am pretty new to Android development I thought I could ask for help.
Does any one know what is causing the ball to be draw like that?
The code is pretty much the same as the one in the link and I have tried to experiment to find a solution but no luck.
well , as you can see on the image , you only drew the ball . instead , you need to re-drew a black background (or whatever that you wish) before each time you draw the ball.
alternatively , you can draw a black area only on the previous position , but you might have problems with it later , when you use more objects.
here's a nice sample, similar to what you do
A quick look and I would have to say you are just drawing on the same surface and never requesting your surfaceview to redraw itself. at the end of the finally block, in the IF Statement use: postInvalidate(); That should cause the surface view to redraw itself.
put this
public void onDraw(Canvas canvas){
canvas.drawColor(Color.BLACK);
.....
}
See how i have done the pendulum simulation at
http://som-itsolutions.blogspot.in/2012/06/android-graphics-and-animation-pendulum.html
You can clone the source code of this project from
https://github.com/sommukhopadhyay/pendulumsimulation
[edit]The answer was wrong, but the comment was helpful so I'll leave this answer up:
Not the question you asked, but there is a problem in your code. In Android you are only allowed to write to the screen in the UI thread. This is the thread that runs all the Activity callbacks, etc. By writing to the screen from BallThread you are risking many odd failures in your program.
How do I implement background scrolling with a gridview? If it sounds vague, I mean like implementing a bookshelf using a gridview where the shelf image is attached to an item in the gridview.
It took me forever to figure this out, so for everybody trying to do this, here's the code from my e-book reader.
It's based on Shelves by Romain Guy so he deserves the credit for the original code.
package net.nightwhistler.pageturner.view;
import net.nightwhistler.pageturner.R;
import net.nightwhistler.pageturner.library.LibraryBook;
import net.nightwhistler.pageturner.library.QueryResult;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.KeyEvent;
import android.widget.GridView;
public class BookCaseView extends GridView {
private Bitmap background;
private int mShelfWidth;
private int mShelfHeight;
private QueryResult<LibraryBook> result;
private LibraryBook selectedBook;
public BookCaseView(Context context, AttributeSet attributes) {
super(context, attributes);
this.setFocusableInTouchMode(true);
this.setClickable(false);
final Bitmap shelfBackground = BitmapFactory.decodeResource(context.getResources(),
R.drawable.shelf_single);
setBackground(shelfBackground);
this.setFocusable(true);
}
public void setBackground(Bitmap background) {
this.background = background;
mShelfWidth = background.getWidth();
mShelfHeight = background.getHeight();
}
protected void onClick( int bookIndex ) {
LibraryBook book = this.result.getItemAt(bookIndex);
this.selectedBook = book;
invalidate();
}
#Override
protected void dispatchDraw(Canvas canvas) {
final int count = getChildCount();
final int top = count > 0 ? getChildAt(0).getTop() : 0;
final int shelfWidth = mShelfWidth;
final int shelfHeight = mShelfHeight;
final int width = getWidth();
final int height = getHeight();
final Bitmap background = this.background;
for (int x = 0; x < width; x += shelfWidth) {
for (int y = top; y < height; y += shelfHeight) {
canvas.drawBitmap(background, x, y, null);
}
//This draws the top pixels of the shelf above the current one
Rect source = new Rect(0, mShelfHeight - top, mShelfWidth, mShelfHeight);
Rect dest = new Rect(x, 0, x + mShelfWidth, top );
canvas.drawBitmap(background, source, dest, null);
}
super.dispatchDraw(canvas);
}
#Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if ( keyCode == KeyEvent.KEYCODE_BACK && this.selectedBook != null ) {
this.selectedBook = null;
invalidate();
return true;
}
return false;
}
}
What I did was to split my background image in n gridview columns and in my gridview getView method add the view background according to the position in the grid.
It worked perfectly.
If you want the code just ask.
My previous answer only adds the background but doesn't let it scroll with the items. what you wan is NightWhistler's answer :)
Sorry for misinterpreting the question.