getWidth() & getHeight() returning 0 inside RecyclerView/Fragment - android

I'm calling the populating method for the RecyclerView inside the OnCreateView method in several fragments, they are then directed to the adapter for which populates the feeds (depending on the context of the Fragment).
At the minute, I am adding tags to an uploaded image from a user on the feed. Those tags require an x and y value (position), in which is a percentage of the container (which is set and saved to a BaaS when they upload). At the moment, the pixel to percentage formula works perfectly but when I try and grab the dimensions of the container, every option I have tried returns 0 every time.
RecyclerViewHolderAllFeeds(View view) {
...
this.imageContainer = (RelativeLayout) view
.findViewById(R.id.image_container);
}
Tag Snippet:
(This function is called inside the bind, to bind all the tags to the uploaded image)
float x = containerWidth - Float.parseFloat(model.getXpoints(o)) / 100 * containerWidth;
float y = containerHeight - Float.parseFloat(model.getYpoints(o)) / 100 * containerHeight;
Log.e("measuredWidth", "" + containerHeight + "" + containerWidth);
Log.e("getPointX", "" + model.getXpoints(o) + "" + model.getYpoints(o));
Log.e("x", "x" + x + "y" + y);
tag.setX(x);
tag.setY(y);
I set the values for containerWidth at the moment in the OnCreateViewHolder method:
RecyclerViewHolderAllFeeds holder = null;
LayoutInflater mInflater = LayoutInflater.from(viewGroup.getContext());
if (viewType == 1) {
// This method will inflate the custom layout and return as viewholde
ViewGroup mainGroup = (ViewGroup) mInflater.inflate(R.layout.feed_and_search_feed_item_row, viewGroup, false);
holder = new RecyclerViewHolderAllFeeds(mainGroup);
containerWidth = getContainerWidth(holder);
containerHeight = getContainerHeight(holder);
} else {
ViewGroup mainGroup = (ViewGroup) mInflater.inflate(R.layout.progress_item, viewGroup, false);
holder = new ProgressViewHolder(mainGroup);
}
return holder;
I have also tried this way inside the onViewAttchedToWindow method:
#Override
public void onViewAttachedToWindow(RecyclerViewHolderAllFeeds holder) {
super.onViewAttachedToWindow(holder);
containerWidth = getContainerWidth(holder);
containerHeight = getContainerHeight(holder);
}
But again x and y values equate to zero.
GetContainerWidth(ViewHolder holder) method:
float getContainerWidth(final RecyclerViewHolderAllFeeds h) {
final int[] width = new int[1];
final ViewTreeObserver observer = h.imageContainer.getViewTreeObserver();
observer.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
width[0] = h.imageContainer.getWidth();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
h.imageContainer.getViewTreeObserver().removeOnGlobalLayoutListener(this);
} else {
h.imageContainer.getViewTreeObserver().removeGlobalOnLayoutListener(this);
}
}
});
return width[0];
}
the same logic is applied to the getContainerHeight(ViewHolder holder) method.
I have also tried it without a global listener on the tree observer and with a listener on the preDraw parameters.
observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
#Override
public boolean onPreDraw() {
height[0] = h.imageContainer.getHeight();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
h.imageContainer.getViewTreeObserver().removeOnPreDrawListener(this);
}
return false;
}
});
Log:
E/measuredWidth&Height: 0.0 0.0
E/getPointX&Y: 70.13 88.00
E/x: x 0.0 y 0.0
Could there be a work around in regards to initializing the layout somewhere else and gathering the information and storing it somewhere? I've exhausted plenty of options. I just feel I haven't seen enough around Fragments and RecyclerViews within the SO community.
Any help will be appreciated.

As #Enzokie mentioned you have to calculate your x and y inside callback after you will know width and height of your imageContainer. So all you need is to move your OnGlobalLayoutListener to onBindViewHolder method, like this:
#Override
public void onBindViewHolder(RecyclerViewHolderAllFeeds h, int position) {
...
final ViewTreeObserver observer = h.imageContainer.getViewTreeObserver();
observer.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
h.imageContainer.getViewTreeObserver().removeOnGlobalLayoutListener(this);
} else {
h.imageContainer.getViewTreeObserver().removeGlobalOnLayoutListener(this);
}
int containerWidth = h.imageContainer.getWidth();
int containerHeight = h.imageContainer.getHeight();
float x = containerWidth - Float.parseFloat(model.getXpoints(o)) / 100 * containerWidth;
float y = containerHeight - Float.parseFloat(model.getYpoints(o)) / 100 * containerHeight;
Log.e("measuredWidth", "" + containerHeight + "" + containerWidth);
Log.e("getPointX", "" + model.getXpoints(o) + "" + model.getYpoints(o));
Log.e("x", "x" + x + "y" + y);
h.tag.setX(x);
h.tag.setY(y);
h.tag.requestLayout();
}
});
...
}
Hope that helps!

Related

GridView with colspan and rowSpan

I have been trying to make a gridview with drag and drop functionality along with one cell of different size. I have already made the the grid drag and drop and its working fine. you can check the code from here
but I want it to be like this and purely dynamic as I will be draging and dropping the other which will be replaced and resized automatically
Updated with new code that accommodates resizing of cells.
Your question refers to GridView but the code you supplied doesn't mention GridView but uses GridLayout instead, so I am assuming that GridLayout is the right layout.
I have put together a demo using a mocked-up layout with one 2x2 tile. I have modified the code that you have supplied to accommodate the 2x2 tile. Other than the code that I added to implement the 2x2 tile, the only other change to MainAcitivity was to the calculateNextIndex method that uses a different way of calculating the index at an (x, y) position. Layouts and the LongPressListener class were also mocked up since they were not supplied.
Here is a video of the demo:
MainActivity.java
public class MainActivity extends AppCompatActivity {
private static final int ITEMS = 10;
private GridLayout mGrid;
private ScrollView mScrollView;
private ValueAnimator mAnimator;
private Boolean isScroll = false;
private GridLayout.Spec m1xSpec = GridLayout.spec(GridLayout.UNDEFINED, 1);
private GridLayout.Spec m2xSpec = GridLayout.spec(GridLayout.UNDEFINED, 2);
private int mBaseWidth;
private int mBaseHeight;
private int mBaseMargin;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mScrollView = (ScrollView) findViewById(R.id.scrollView);
mScrollView.setSmoothScrollingEnabled(true);
mGrid = (GridLayout) findViewById(R.id.grid);
mGrid.setOnDragListener(new DragListener());
final LayoutInflater inflater = LayoutInflater.from(this);
GridLayout.LayoutParams lp;
DisplayMetrics displayMetrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
float dpiToPx = displayMetrics.density;
View view = inflater.inflate(R.layout.item, mGrid, false);
lp = (GridLayout.LayoutParams) view.getLayoutParams();
mBaseWidth = lp.width;
mBaseHeight = lp.height;
mBaseMargin = lp.rightMargin;
for (int i = 0; i < ITEMS; i++) {
final View itemView = inflater.inflate(R.layout.item, mGrid, false);
final TextView text = (TextView) itemView.findViewById(R.id.text);
text.setText(String.valueOf(i + 1));
itemView.setOnLongClickListener(new LongPressListener());
lp = (i == 0) ? make2x2LayoutParams(itemView) : make1x1LayoutParams(itemView);
mGrid.addView(itemView, lp);
}
}
private GridLayout.LayoutParams make2x2LayoutParams(View view) {
GridLayout.LayoutParams lp = (GridLayout.LayoutParams) view.getLayoutParams();
lp.width = mBaseWidth * 2 + 2 * mBaseMargin;
lp.height = mBaseHeight * 2 + 2 * mBaseMargin;
lp.rowSpec = m2xSpec;
lp.columnSpec = m2xSpec;
lp.setMargins(mBaseMargin, mBaseMargin, mBaseMargin, mBaseMargin);
return lp;
}
private GridLayout.LayoutParams make1x1LayoutParams(View view) {
GridLayout.LayoutParams lp = (GridLayout.LayoutParams) view.getLayoutParams();
lp.width = mBaseWidth;
lp.height = mBaseHeight;
lp.setMargins(mBaseMargin, mBaseMargin, mBaseMargin, mBaseMargin);
lp.rowSpec = m1xSpec;
lp.columnSpec = m1xSpec;
return lp;
}
private int mDraggedIndex;
class DragListener implements View.OnDragListener {
#Override
public boolean onDrag(View v, DragEvent event) {
final View view = (View) event.getLocalState();
int index = calculateNextIndex(event.getX(), event.getY());
View child;
switch (event.getAction()) {
case DragEvent.ACTION_DRAG_STARTED:
mDraggedIndex = index;
break;
case DragEvent.ACTION_DRAG_LOCATION:
if (view == v) return true;
// get the new list index
final Rect rect = new Rect();
mScrollView.getHitRect(rect);
final int scrollY = mScrollView.getScrollY();
if (event.getY() - scrollY > mScrollView.getBottom() - 250) {
startScrolling(scrollY, mGrid.getHeight());
} else if (event.getY() - scrollY < mScrollView.getTop() + 250) {
startScrolling(scrollY, 0);
} else {
stopScrolling();
}
child = mGrid.getChildAt(0);
if (index == 0) {
child.setLayoutParams(make1x1LayoutParams(child));
view.setLayoutParams(make2x2LayoutParams(view));
} else if (mDraggedIndex == 0) {
view.setLayoutParams(make1x1LayoutParams(view));
child.setLayoutParams(make2x2LayoutParams(child));
} else {
child.setLayoutParams(make2x2LayoutParams(child));
view.setLayoutParams(make1x1LayoutParams(view));
}
mGrid.removeView(view);
mGrid.addView(view, index);
break;
case DragEvent.ACTION_DROP:
for (int i = 0; i < mGrid.getChildCount(); i++) {
child = mGrid.getChildAt(i);
child.setLayoutParams(make1x1LayoutParams(child));
}
mGrid.removeView(view);
if (index == 0) {
view.setLayoutParams(make2x2LayoutParams(view));
}
mGrid.addView(view, index);
view.setVisibility(View.VISIBLE);
mGrid.getChildAt(0).setLayoutParams(make2x2LayoutParams(mGrid.getChildAt(0)));
break;
case DragEvent.ACTION_DRAG_ENDED:
if (!event.getResult()) {
view.setVisibility(View.VISIBLE);
}
break;
}
return true;
}
}
private void startScrolling(int from, int to) {
if (from != to && mAnimator == null) {
isScroll = true;
mAnimator = new ValueAnimator();
mAnimator.setInterpolator(new OvershootInterpolator());
mAnimator.setDuration(Math.abs(to - from));
mAnimator.setIntValues(from, to);
mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
mScrollView.smoothScrollTo(0, (int) valueAnimator.getAnimatedValue());
}
});
mAnimator.addListener(new AnimatorListenerAdapter() {
#Override
public void onAnimationEnd(Animator animation) {
isScroll = false;
mAnimator = null;
}
});
mAnimator.start();
}
}
private void stopScrolling() {
if (mAnimator != null) {
mAnimator.cancel();
}
}
private int calculateNextIndexOld(float x, float y) {
// calculate which column to move to
final float cellWidth = mGrid.getWidth() / mGrid.getColumnCount();
final int column = (int) (x / cellWidth);
final float cellHeight = mGrid.getHeight() / mGrid.getRowCount();
final int row = (int) Math.floor(y / cellHeight);
int index = row * mGrid.getColumnCount() + column;
if (index >= mGrid.getChildCount()) {
index = mGrid.getChildCount() - 1;
}
Log.d("MainActivity", "<<<<index=" + index);
return index;
}
private int calculateNextIndex(float x, float y) {
// calculate which column to move to
int index;
for (index = 0; index < mGrid.getChildCount(); index++) {
View child = mGrid.getChildAt(index);
Rect rect = new Rect();
child.getHitRect(rect);
if (x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom) {
break;
}
}
if (index >= mGrid.getChildCount()) {
// Move into empty cell? Calculate based upon uniform cell sizes.
index = calculateNextIndexOld(x, y);
}
if (index >= mGrid.getChildCount()) {
// Can't determine where to put it? Add it to the end.
index = mGrid.getChildCount() - 1;
}
return index;
}
}
If you work with the demo a little, you will see that it is possible to move tiles such that a 1x1 tile gap is opened up. This may be OK, but the code may need to be reworked a little if not.
You can try :
https://github.com/askerov/DynamicGrid
I hope it can help your problem!

Relative Layout get Width/Height return 0

I need to get width and height of Relative layout, however I do not know when the Relative Layout is ready to provide me these two attributes. My activity has several tabs and I call the getWidth() and getHeight() in onCreateView() after inflater.inflate(R.layout.fragment_pizza, container, false);, however it returns 0 for both of them, so they are not apparently ready. Is there some kind of event like afterCreateView() or something similar where I can call these two methods and get the actual width and height?
Code:
public class PizzaFragment extends Fragment {
private static final String TAG = "PizzaFragment";
private Controller controller;
private static final int BUTTON_WIDTH = 70;
private static final int BUTTON_HEIGHT = 20;
private static final int MARGIN_LEFT = 80;
private static final int MARGIN_BOTTOM = 30;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
controller = Controller.getInstance();
controller.loadProducts("pizza", getActivity().getApplicationContext());
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_pizza, container, false);
RelativeLayout layout = (RelativeLayout) view.findViewById(R.id.rl_order_pizza);
Log.d(TAG, "Width: " + layout.getWidth());
Log.d(TAG, "Height: " + layout.getHeight());
int currentX = 10;
int currentY = 10;
RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(BUTTON_WIDTH, BUTTON_HEIGHT);
for (Product product: controller.getProducts("pizza")){
Button tempButton = new Button(getActivity());
tempButton.setId(product.getId());
tempButton.setText(product.getName());
if (layout.getWidth() < currentX + MARGIN_LEFT){
currentX = 10;
currentY = MARGIN_BOTTOM;
}
else{
currentX += MARGIN_LEFT;
}
Log.d(TAG, "CurrentY: " + currentY);
Log.d(TAG, "CurrentX: " + currentX);
layoutParams.leftMargin = currentX;
layoutParams.bottomMargin = currentY;
tempButton.setLayoutParams(layoutParams);
layout.addView(tempButton);
}
return view;
}
}
My suggestion is to use a global layout listener like this in onCreateView:
YOUR_LAYOUT_INSTANCE = view.findViewById(R.id.YOUR_LAYOUT_ID);
...
YOUR_LAYOUT_INSTANCE.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener()
{
#Override
public void onGlobalLayout()
{
// gets called after layout has been done but before display.
if (Build.VERSION.SDK_INT < 16) {
YOUR_LAYOUT_INSTANCE.getViewTreeObserver().removeGlobalOnLayoutListener(this);
} else {
YOUR_LAYOUT_INSTANCE.getViewTreeObserver().removeOnGlobalLayoutListener(this);
}
// get width and height
}
});
addOnGlobalLayoutListener is deprecated use removeOnGlobalLayoutListener .For better handling extending the solution provided above by #fasteque
YOUR_LAYOUT_INSTANCE.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener()
{
#Override
public void onGlobalLayout()
{
// gets called after layout has been done but before display.
if (Build.VERSION.SDK_INT < 16) {
YOUR_LAYOUT_INSTANCE.getViewTreeObserver().removeGlobalOnLayoutListener(this);
}
else {
YOUR_LAYOUT_INSTANCE.getViewTreeObserver().removeOnGlobalLayoutListener(this);
}
// get width and height
}
});

Android Gallery zoom in/out

Hi I am using the Gallery widget to show images downloaded from the internet.
to show several images and I would like to have a gradual zoom while people slide up and down on the screen. I know how to implement the touch event the only thing I don't know how to make the whole gallery view grow gradually. I don't want to zoom in on one image I want the whole gallery to zoom in/out gradually.
EDIT3: I manage to zoom the visible part of the gallery but the problem is I need to find a way for the gallery to find out about it and update it's other children too.
What happens is if 3 images are visible then you start zooming and the gallery does get smaller, so do the images but what I would like in this case is more images to be visible but I don't know how to reach this desired effect. Here's the entire code:
public class Gallery1 extends Activity implements OnTouchListener {
private static final String TAG = "GalleryTest";
private float zoom=0.0f;
// Remember some things for zooming
PointF start = new PointF();
PointF mid = new PointF();
Gallery g;
LinearLayout layout2;
private ImageAdapter ad;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.gallery_1);
layout2=(LinearLayout) findViewById(R.id.layout2);
// Reference the Gallery view
g = (Gallery) findViewById(R.id.gallery);
// Set the adapter to our custom adapter (below)
ad=new ImageAdapter(this);
g.setAdapter(ad);
layout2.setOnTouchListener(this);
}
public void zoomList(boolean increase) {
Log.i(TAG, "startig animation");
AnimatorSet set = new AnimatorSet();
set.playTogether(
ObjectAnimator.ofFloat(g, "scaleX", zoom),
ObjectAnimator.ofFloat(g, "scaleY", zoom)
);
set.addListener(new AnimatorListener() {
#Override
public void onAnimationStart(Animator animation) {
}
#Override
public void onAnimationRepeat(Animator animation) {
// TODO Auto-generated method stub
}
#Override
public void onAnimationEnd(Animator animation) {
}
#Override
public void onAnimationCancel(Animator animation) {
// TODO Auto-generated method stub
}
});
set.setDuration(100).start();
}
public class ImageAdapter extends BaseAdapter {
private static final int ITEM_WIDTH = 136;
private static final int ITEM_HEIGHT = 88;
private final int mGalleryItemBackground;
private final Context mContext;
private final Integer[] mImageIds = {
R.drawable.gallery_photo_1,
R.drawable.gallery_photo_2,
R.drawable.gallery_photo_3,
R.drawable.gallery_photo_4,
R.drawable.gallery_photo_5,
R.drawable.gallery_photo_6,
R.drawable.gallery_photo_7,
R.drawable.gallery_photo_8
};
private final float mDensity;
public ImageAdapter(Context c) {
mContext = c;
// See res/values/attrs.xml for the <declare-styleable> that defines
// Gallery1.
TypedArray a = obtainStyledAttributes(R.styleable.Gallery1);
mGalleryItemBackground = a.getResourceId(
R.styleable.Gallery1_android_galleryItemBackground, 1);
a.recycle();
mDensity = c.getResources().getDisplayMetrics().density;
}
public int getCount() {
return mImageIds.length;
}
public Object getItem(int position) {
return position;
}
public long getItemId(int position) {
return position;
}
public View getView(int position, View convertView, ViewGroup parent) {
ImageView imageView;
if (convertView == null) {
convertView = new ImageView(mContext);
imageView = (ImageView) convertView;
imageView.setScaleType(ImageView.ScaleType.FIT_XY);
imageView.setLayoutParams(new Gallery.LayoutParams(
(int) (ITEM_WIDTH * mDensity + 0.5f),
(int) (ITEM_HEIGHT * mDensity + 0.5f)));
} else {
imageView = (ImageView) convertView;
}
imageView.setImageResource(mImageIds[position]);
return imageView;
}
}
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_MOVE
&& event.getPointerCount() > 1) {
midPoint(mid, event);
if(mid.y > start.y){
Log.i(TAG, "Going down (Math.abs(mid.y - start.y)= "+(Math.abs(mid.y - start.y))+" and zoom="+zoom); // going down so increase
if ((Math.abs(mid.y - start.y) > 10) && (zoom<2.5f)){
zoom=zoom+0.1f;
midPoint(start, event);
zoomList(true);
}
return true;
}else if(mid.y < start.y){
Log.i(TAG, "Going up (Math.abs(mid.y - start.y)= "+(Math.abs(mid.y - start.y))+" and zoom="+zoom); //smaller
if ((Math.abs(mid.y - start.y) > 10) &&(zoom>0.1)){
midPoint(start, event);
zoom=zoom-0.1f;
zoomList(false);
}
return true;
}
}
else if (event.getAction() == MotionEvent.ACTION_POINTER_DOWN) {
Log.e(TAG, "Pointer went down: " + event.getPointerCount());
return true;
}
else if (event.getAction() == MotionEvent.ACTION_UP) {
Log.i(TAG, "Pointer going up");
return true;
}
else if (event.getAction() == MotionEvent.ACTION_DOWN) {
Log.i(TAG, "Pointer going down");
start.set(event.getX(), event.getY());
return true;
}
return false;
// indicate event was handled or not
}
private void midPoint(PointF point, MotionEvent event) {
float x = event.getX(0) + event.getX(1);
float y = event.getY(0) + event.getY(1);
point.set(x / 2, y / 2);
}
I realise I will probably have to extend the Gallery or even another View group or create my own class but I don't know where to start: which method use the one responsible for scaling...
EDIT4: I don't know if he question is clear enough. Here is an example of states:
State one: initial state, we have 3 images in view
State 2: we detect vertical touches going up with 2 fingers = we have to zoom out
state 3: we start zooming = animation on the gallery or on the children???
state 4: gallery detects that it's 3 children are smaller
state 5: gallery adds 1 /more children according to the new available space
LAST UPDATE:
Thanks to all that have posted but I have finally reached a conclusion and that is to not use Gallery at all:
1. It's deprecated
2. It's not customizable enough for my case
If you want to animate several images at once you may want to consider using OpenGl, I am using libgdx library:
https://github.com/libgdx/libgdx
The following ScalingGallery implementation might be of help.
This gallery subclass overrides the getChildStaticTransformation(View child, Transformation t) method in which the scaling is performed. You can further customize the scaling parameters to fit your own needs.
Please note the ScalingGalleryItemLayout.java class. This is necessary because after you have performed the scaling operationg on the child views, their hit boxes are no longer valid so they must be updated from with the getChildStaticTransformation(View child, Transformation t) method.
This is done by wrapping each gallery item in a ScalingGalleryItemLayout which extends a LinearLayout. Again, you can customize this to fit your own needs if a LinearLayout does not meet your needs for layout out your gallery items.
File : /src/com/example/ScalingGallery.java
/**
* A Customized Gallery component which alters the size and position of its items based on their position in the Gallery.
*/
public class ScalingGallery extends Gallery {
public static final int ITEM_SPACING = -20;
private static final float SIZE_SCALE_MULTIPLIER = 0.25f;
private static final float ALPHA_SCALE_MULTIPLIER = 0.5f;
private static final float X_OFFSET = 20.0f;
/**
* Implemented by child view to adjust the boundaries after it has been matrix transformed.
*/
public interface SetHitRectInterface {
public void setHitRect(RectF newRect);
}
/**
* #param context
* Context that this Gallery will be used in.
* #param attrs
* Attributes for this Gallery (via either xml or in-code)
*/
public ScalingGallery(Context context, AttributeSet attrs) {
super(context, attrs);
setStaticTransformationsEnabled(true);
setChildrenDrawingOrderEnabled(true);
}
/**
* {#inheritDoc}
*
* #see #setStaticTransformationsEnabled(boolean)
*
* This is where the scaling happens.
*/
protected boolean getChildStaticTransformation(View child, Transformation t) {
child.invalidate();
t.clear();
t.setTransformationType(Transformation.TYPE_BOTH);
// Position of the child in the Gallery (... +2 +1 0 -1 -2 ... 0 being the middle)
final int childPosition = getSelectedItemPosition() - getPositionForView(child);
final int childPositionAbs = (int) Math.abs(childPosition);
final float left = child.getLeft();
final float top = child.getTop();
final float right = child.getRight();
final float bottom = child.getBottom();
Matrix matrix = t.getMatrix();
RectF modifiedHitBox = new RectF();
// Change alpha, scale and translate non-middle child views.
if (childPosition != 0) {
final int height = child.getMeasuredHeight();
final int width = child.getMeasuredWidth();
// Scale the size.
float scaledSize = 1.0f - (childPositionAbs * SIZE_SCALE_MULTIPLIER);
if (scaledSize < 0) {
scaledSize = 0;
}
matrix.setScale(scaledSize, scaledSize);
float moveX = 0;
float moveY = 0;
// Moving from right to left -- linear move since the scaling is done with respect to top-left corner of the view.
if (childPosition < 0) {
moveX = ((childPositionAbs - 1) * SIZE_SCALE_MULTIPLIER * width) + X_OFFSET;
moveX *= -1;
} else { // Moving from left to right -- sum of the previous positions' x displacements.
// X(n) = X(0) + X(1) + X(2) + ... + X(n-1)
for (int i = childPositionAbs; i > 0; i--) {
moveX += (i * SIZE_SCALE_MULTIPLIER * width);
}
moveX += X_OFFSET;
}
// Moving down y-axis is linear.
moveY = ((childPositionAbs * SIZE_SCALE_MULTIPLIER * height) / 2);
matrix.postTranslate(moveX, moveY);
// Scale alpha value.
final float alpha = (1.0f / childPositionAbs) * ALPHA_SCALE_MULTIPLIER;
t.setAlpha(alpha);
// Calculate new hit box. Since we moved the child, the hitbox is no longer lined up with the new child position.
final float newLeft = left + moveX;
final float newTop = top + moveY;
final float newRight = newLeft + (width * scaledSize);
final float newBottom = newTop + (height * scaledSize);
modifiedHitBox = new RectF(newLeft, newTop, newRight, newBottom);
} else {
modifiedHitBox = new RectF(left, top, right, bottom);
}
// update child hit box so you can tap within the child's boundary
((SetHitRectInterface) child).setHitRect(modifiedHitBox);
return true;
}
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
// Helps to smooth out jittering during scrolling.
// read more - http://www.unwesen.de/2011/04/17/android-jittery-scrolling-gallery/
final int viewsOnScreen = getLastVisiblePosition() - getFirstVisiblePosition();
if (viewsOnScreen <= 0) {
super.onLayout(changed, l, t, r, b);
}
}
private int mLastDrawnPosition;
#Override
protected int getChildDrawingOrder(int childCount, int i) {
//Reset the last position variable every time we are starting a new drawing loop
if (i == 0) {
mLastDrawnPosition = 0;
}
final int centerPosition = getSelectedItemPosition() - getFirstVisiblePosition();
if (i == childCount - 1) {
return centerPosition;
} else if (i >= centerPosition) {
mLastDrawnPosition++;
return childCount - mLastDrawnPosition;
} else {
return i;
}
}
}
File : /src/com/example/ScalingGalleryItemLayout.java
public class ScalingGalleryItemLayout extends LinearLayout implements SetHitRectInterface {
public ScalingGalleryItemLayout(Context context) {
super(context);
}
public ScalingGalleryItemLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ScalingGalleryItemLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
private Rect mTransformedRect;
#Override
public void setHitRect(RectF newRect) {
if (newRect == null) {
return;
}
if (mTransformedRect == null) {
mTransformedRect = new Rect();
}
newRect.round(mTransformedRect);
}
#Override
public void getHitRect(Rect outRect) {
if (mTransformedRect == null) {
super.getHitRect(outRect);
} else {
outRect.set(mTransformedRect);
}
}
}
File : /res/layout/ScaledGalleryItemLayout.xml
<?xml version="1.0" encoding="utf-8"?>
<com.example.ScalingGalleryItemLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/gallery_item_layout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center"
android:orientation="vertical"
android:padding="5dp" >
<ImageView
android:id="#+id/gallery_item_image"
android:layout_width="360px"
android:layout_height="210px"
android:layout_gravity="center"
android:antialias="true"
android:background="#drawable/gallery_item_button_selector"
android:cropToPadding="true"
android:padding="35dp"
android:scaleType="centerInside" />
<TextView
android:id="#+id/gallery_item_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:textColor="#drawable/white"
android:textSize="30sp" />
</com.example.ScalingGalleryItemLayout>
To keep the state of the animation after it is done, just do this on your animation:
youranim.setFillAfter(true);
Edit :
In my project, I use this method and i think, it's help you :
http://developer.sonymobile.com/wp/2011/04/12/how-to-take-advantage-of-the-pinch-to-zoom-feature-in-your-xperia%E2%84%A2-10-apps-part-1/
U can do Image Zoom pinch option for gallery also.
by using below code lines:
you can download the example.
https://github.com/alvinsj/android-image-gallery/downloads
I hope this example will help to u..if u have any queries ask me.....
This is solution
integrate gallery component in android with gesture-image library
gesture-imageView
And here is full sample code
SampleCode

Help with Android UI ListView problems

To understand this question, first read how this method works.
I am trying to implements a drag and drop ListView, it's going alright but have run into
a road block. So I don't have to handled everything, I am intercepting(but returning false) MotionEvents sent to the ListView, letting it handle scrolling and stuff. When I want to start dragging a item, I then return true and handled all the dragging stuff. Everything is working fine except for one thing. The drag(drag and drop) is started when it is determined that a long press as a occurred(in onInterceptTouchEvent). I get the Bitmap for the image that I drag around like so. itemPositition being the index of the item that was selected.
(omitting irrelevant parts)
...
View dragItem = mListView.getChildAt(itemPosition);
dragItem.setDrawingCacheEnabled(true);
Bitmap bitmap = Bitmap.createBitmap(dragItem.getDrawingCache());
mDragImage = new ImageView(mContext);
mDragImage.setImageBitmap(bitmap);
...
The problem is, mDragImage is a solid black like this.
But, if I don't let ListView handle anything. As in, I start the drag on ACTION_DOWN and stop on ACTION_UP, mDragImage looks has expected(but I obviously lose scrolling abilities).
Since the drag is started with a long press, the ListView is given the opportunity to do things before the long press occurs. This is my guess as to why this is happening. When a item is pressed, it is highlighted by the ListView. Somewhere in doing so, it is messing with the bitmap. So when I go to get it, it's in a weird state(all black).
I see two options for fixing this, neither of which I know how to do.
Create a image from scratch.
Handle the highlighting myself(if that is the problem).
Option two seems a better one to me, except that I looked at the documentation and the source code and could not find out how to do so. Here are some things I have done/tried.
I set setOnItemClickListener(...) and
setOnItemSelectedListener(...) with a empty method(highlighting
still happens). (Before anyone suggests it, calling
setOnClickListener results in a runtime error.)
I also looked into trying to get the ListView to make a new item
(for option 2), but could not find a way.
Spent 45ish minutes looking through the source code and
documentation trying to pinpoint where the highlighting was
happening(I never found it).
Any help fixing this would be appreciated.
(EDIT1 START)
So I don't actually know if onLongClickListener is working, I made an error before thinking it was. I am trying to set it up right now, will update when I find out if it does.
(EDIT1 END)
Last minute edit before post. I tried using onLongClickListener just now, and the image is good. I would still like to know if there is another way. How I have to use onLongClickListener to get things working is ugly, but it works. I also spent so much time trying to figure this out, it would be nice to find out the answer. I still want to be able to change/handle the highlight color, the default orangeish color is not pretty. Oh and sorry about the length of the post. I could not think of way of making it shorter, while supplying all the information I thought was needed.
use this code, it's allows operation drug and drop in ListView:
public class DraggableListView extends ListView {
private static final String LOG_TAG = "tasks365";
private static final int END_OF_LIST_POSITION = -2;
private DropListener mDropListener;
private int draggingItemHoverPosition;
private int dragStartPosition; // where was the dragged item originally
private int mUpperBound; // scroll the view when dragging point is moving out of this bound
private int mLowerBound; // scroll the view when dragging point is moving out of this bound
private int touchSlop;
private Dragging dragging;
private GestureDetector longPressDetector;
public DraggableListView(Context context, AttributeSet attrs) {
this(context, attrs, android.R.attr.listViewStyle);
}
public DraggableListView(final Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
touchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
longPressDetector = new GestureDetector(getContext(), new SimpleOnGestureListener() {
#Override
public void onLongPress(final MotionEvent e) {
int x = (int) e.getX();
final int y = (int) e.getY();
int itemnum = pointToPosition(x, y);
if (itemnum == AdapterView.INVALID_POSITION) {
return;
}
if (dragging != null) {
dragging.stop();
dragging = null;
}
final View item = getChildAt(itemnum - getFirstVisiblePosition());
item.setPressed(false);
dragging = new Dragging(getContext());
dragging.start(y, ((int) e.getRawY()) - y, item);
draggingItemHoverPosition = itemnum;
dragStartPosition = draggingItemHoverPosition;
int height = getHeight();
mUpperBound = Math.min(y - touchSlop, height / 3);
mLowerBound = Math.max(y + touchSlop, height * 2 / 3);
}
});
setOnItemLongClickListener(new OnItemLongClickListener() {
#SuppressWarnings("unused")
public boolean onItemLongClick(AdapterView<?> paramAdapterView, View paramView, int paramInt, long paramLong) {
// Return true to let AbsListView reset touch mode
// Without this handler, the pressed item will keep highlight.
return true;
}
});
}
/* pointToPosition() doesn't consider invisible views, but we need to, so implement a slightly different version. */
private int myPointToPosition(int x, int y) {
if (y < 0) {
return getFirstVisiblePosition();
}
Rect frame = new Rect();
final int count = getChildCount();
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
child.getHitRect(frame);
if (frame.contains(x, y)) {
return getFirstVisiblePosition() + i;
}
}
if ((x >= frame.left) && (x < frame.right) && (y >= frame.bottom)) {
return END_OF_LIST_POSITION;
}
return INVALID_POSITION;
}
#Override
public boolean onTouchEvent(MotionEvent ev) {
if (longPressDetector.onTouchEvent(ev)) {
return true;
}
if ((dragging == null) || (mDropListener == null)) {
// it is not dragging, or there is no drop listener
return super.onTouchEvent(ev);
}
int action = ev.getAction();
switch (ev.getAction()) {
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
dragging.stop();
dragging = null;
if (mDropListener != null) {
if (draggingItemHoverPosition == END_OF_LIST_POSITION) {
mDropListener.drop(dragStartPosition, getCount() - 1);
} else if (draggingItemHoverPosition != INVALID_POSITION) {
mDropListener.drop(dragStartPosition, draggingItemHoverPosition);
}
}
resetViews();
break;
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_MOVE:
int x = (int) ev.getX();
int y = (int) ev.getY();
dragging.drag(x, y);
int position = dragging.calculateHoverPosition();
if (position != INVALID_POSITION) {
if ((action == MotionEvent.ACTION_DOWN) || (position != draggingItemHoverPosition)) {
draggingItemHoverPosition = position;
doExpansion();
}
scrollList(y);
}
break;
}
return true;
}
private void doExpansion() {
int expanItemViewIndex = draggingItemHoverPosition - getFirstVisiblePosition();
if (draggingItemHoverPosition >= dragStartPosition) {
expanItemViewIndex++;
}
// Log.v(LOG_TAG, "Dragging item hovers over position " + draggingItemHoverPosition + ", expand item at index "
// + expanItemViewIndex);
View draggingItemOriginalView = getChildAt(dragStartPosition - getFirstVisiblePosition());
for (int i = 0;; i++) {
View itemView = getChildAt(i);
if (itemView == null) {
break;
}
ViewGroup.LayoutParams params = itemView.getLayoutParams();
int height = LayoutParams.WRAP_CONTENT;
if (itemView.equals(draggingItemOriginalView)) {
height = 1;
} else if (i == expanItemViewIndex) {
height = itemView.getHeight() + dragging.getDraggingItemHeight();
}
params.height = height;
itemView.setLayoutParams(params);
}
}
/**
* Reset view to original height.
*/
private void resetViews() {
for (int i = 0;; i++) {
View v = getChildAt(i);
if (v == null) {
layoutChildren(); // force children to be recreated where needed
v = getChildAt(i);
if (v == null) {
break;
}
}
ViewGroup.LayoutParams params = v.getLayoutParams();
params.height = LayoutParams.WRAP_CONTENT;
v.setLayoutParams(params);
}
}
private void resetScrollBounds(int y) {
int height = getHeight();
if (y >= height / 3) {
mUpperBound = height / 3;
}
if (y <= height * 2 / 3) {
mLowerBound = height * 2 / 3;
}
}
private void scrollList(int y) {
resetScrollBounds(y);
int height = getHeight();
int speed = 0;
if (y > mLowerBound) {
// scroll the list up a bit
speed = y > (height + mLowerBound) / 2 ? 16 : 4;
} else if (y < mUpperBound) {
// scroll the list down a bit
speed = y < mUpperBound / 2 ? -16 : -4;
}
if (speed != 0) {
int ref = pointToPosition(0, height / 2);
if (ref == AdapterView.INVALID_POSITION) {
//we hit a divider or an invisible view, check somewhere else
ref = pointToPosition(0, height / 2 + getDividerHeight() + 64);
}
View v = getChildAt(ref - getFirstVisiblePosition());
if (v != null) {
int pos = v.getTop();
setSelectionFromTop(ref, pos - speed);
}
}
}
public void setDropListener(DropListener l) {
mDropListener = l;
}
public interface DropListener {
void drop(int from, int to);
}
class Dragging {
private Context context;
private WindowManager windowManager;
private WindowManager.LayoutParams mWindowParams;
private ImageView mDragView;
private Bitmap mDragBitmap;
private int coordOffset;
private int mDragPoint; // at what offset inside the item did the user grab it
private int draggingItemHeight;
private int x;
private int y;
private int lastY;
public Dragging(Context context) {
this.context = context;
windowManager = (WindowManager) context.getSystemService("window");
}
/**
* #param y
* #param offset - the difference in y axis between screen coordinates and coordinates in this view
* #param view - which view is dragged
*/
public void start(int y, int offset, View view) {
this.y = y;
lastY = y;
this.coordOffset = offset;
mDragPoint = y - view.getTop();
draggingItemHeight = view.getHeight();
mDragView = new ImageView(context);
mDragView.setBackgroundResource(android.R.drawable.alert_light_frame);
// Create a copy of the drawing cache so that it does not get recycled
// by the framework when the list tries to clean up memory
view.setDrawingCacheEnabled(true);
mDragBitmap = Bitmap.createBitmap(view.getDrawingCache());
mDragView.setImageBitmap(mDragBitmap);
mWindowParams = new WindowManager.LayoutParams();
mWindowParams.gravity = Gravity.TOP;
mWindowParams.x = 0;
mWindowParams.y = y - mDragPoint + coordOffset;
mWindowParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
mWindowParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
mWindowParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
| WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
mWindowParams.format = PixelFormat.TRANSLUCENT;
mWindowParams.windowAnimations = 0;
windowManager.addView(mDragView, mWindowParams);
}
public void drag(int x, int y) {
lastY = this.y;
this.x = x;
this.y = y;
mWindowParams.y = y - mDragPoint + coordOffset;
windowManager.updateViewLayout(mDragView, mWindowParams);
}
public void stop() {
if (mDragView != null) {
windowManager.removeView(mDragView);
mDragView.setImageDrawable(null);
mDragView = null;
}
if (mDragBitmap != null) {
mDragBitmap.recycle();
mDragBitmap = null;
}
}
public int getDraggingItemHeight() {
return draggingItemHeight;
}
public int calculateHoverPosition() {
int adjustedY = (int) (y - mDragPoint + (Math.signum(y - lastY) + 2) * draggingItemHeight / 2);
// Log.v(LOG_TAG, "calculateHoverPosition(): lastY=" + lastY + ", y=" + y + ", adjustedY=" + adjustedY);
int pos = myPointToPosition(0, adjustedY);
if (pos >= 0) {
if (pos >= dragStartPosition) {
pos -= 1;
}
}
return pos;
}
}
}

Android: how to check if a View inside of ScrollView is visible?

I have a ScrollView which holds a series of Views. I would like to be able to determine if a view is currently visible (if any part of it is currently displayed by the ScrollView). I would expect the below code to do this, surprisingly it does not:
Rect bounds = new Rect();
view.getDrawingRect(bounds);
Rect scrollBounds = new Rect(scroll.getScrollX(), scroll.getScrollY(),
scroll.getScrollX() + scroll.getWidth(), scroll.getScrollY() + scroll.getHeight());
if(Rect.intersects(scrollBounds, bounds))
{
//is visible
}
This works:
Rect scrollBounds = new Rect();
scrollView.getHitRect(scrollBounds);
if (imageView.getLocalVisibleRect(scrollBounds)) {
// Any portion of the imageView, even a single pixel, is within the visible window
} else {
// NONE of the imageView is within the visible window
}
Use View#getHitRect instead of View#getDrawingRect on the view you're testing. You can use View#getDrawingRect on the ScrollView instead of calculating explicitly.
Code from View#getDrawingRect:
public void getDrawingRect(Rect outRect) {
outRect.left = mScrollX;
outRect.top = mScrollY;
outRect.right = mScrollX + (mRight - mLeft);
outRect.bottom = mScrollY + (mBottom - mTop);
}
Code from View#getHitRect:
public void getHitRect(Rect outRect) {
outRect.set(mLeft, mTop, mRight, mBottom);
}
If you want to detect that the view is FULLY visible:
private boolean isViewVisible(View view) {
Rect scrollBounds = new Rect();
mScrollView.getDrawingRect(scrollBounds);
float top = view.getY();
float bottom = top + view.getHeight();
if (scrollBounds.top < top && scrollBounds.bottom > bottom) {
return true;
} else {
return false;
}
}
This extension help detect view fully visible.
It also work if your View is a child of child of ... of ScrollView (eg: ScrollView -> LinearLayout -> ContraintLayout -> ... -> YourView).
fun ScrollView.isViewVisible(view: View): Boolean {
val scrollBounds = Rect()
this.getDrawingRect(scrollBounds)
var top = 0f
var temp = view
while (temp !is ScrollView){
top += (temp).y
temp = temp.parent as View
}
val bottom = top + view.height
return scrollBounds.top < top && scrollBounds.bottom > bottom
}
Note
1) view.getY() and view.getX() return the x,y value to FIRST PARENT.
2) Here is example about how getDrawingRect will return
Link
My Solution is use NestedScrollView Scroll element:
final Rect scrollBounds = new Rect();
scroller.getHitRect(scrollBounds);
scroller.setOnScrollChangeListener(new NestedScrollView.OnScrollChangeListener() {
#Override
public void onScrollChange(NestedScrollView v, int scrollX, int scrollY, int oldScrollX, int oldScrollY) {
if (myBtn1 != null) {
if (myBtn1.getLocalVisibleRect(scrollBounds)) {
if (!myBtn1.getLocalVisibleRect(scrollBounds)
|| scrollBounds.height() < myBtn1.getHeight()) {
Log.i(TAG, "BTN APPEAR PARCIALY");
} else {
Log.i(TAG, "BTN APPEAR FULLY!!!");
}
} else {
Log.i(TAG, "No");
}
}
}
});
}
To expand a bit on Bill Mote's answer using getLocalVisibleRect, you may want to check if the view is only partially visible:
Rect scrollBounds = new Rect();
scrollView.getHitRect(scrollBounds);
if (!imageView.getLocalVisibleRect(scrollBounds)
|| scrollBounds.height() < imageView.getHeight()) {
// imageView is not within or only partially within the visible window
} else {
// imageView is completely visible
}
public static int getVisiblePercent(View v) {
if (v.isShown()) {
Rect r = new Rect();
v.getGlobalVisibleRect(r);
double sVisible = r.width() * r.height();
double sTotal = v.getWidth() * v.getHeight();
return (int) (100 * sVisible / sTotal);
} else {
return -1;
}
}
I faced the same problem today. While Googling and reading Android reference I found this post and a method I ended up using instead;
public final boolean getLocalVisibleRect (Rect r)
Nice of them not to only providing Rect but also boolean indicating if View visible at all. On negative side this method is undocumented :(
I you want to detect if your View is fully visible, try with this method:
private boolean isViewVisible(View view) {
Rect scrollBounds = new Rect();
mScrollView.getDrawingRect(scrollBounds);
float top = view.getY();
float bottom = top + view.getHeight();
if (scrollBounds.top < top && scrollBounds.bottom > bottom) {
return true; //View is visible.
} else {
return false; //View is NOT visible.
}
}
Strictly speaking you can get the visibility of a view with:
if (myView.getVisibility() == View.VISIBLE) {
//VISIBLE
} else {
//INVISIBLE
}
The posible constant values of the visibility in a View are:
VISIBLE
This view is visible. Use with setVisibility(int) and android:visibility.
INVISIBLE
This view is invisible, but it still takes up space for layout purposes. Use with setVisibility(int) and android:visibility.
GONE
This view is invisible, and it doesn't take any space for layout purposes. Use with setVisibility(int) and android:visibility.
Kotlin way;
An extension for listing scroll view's scroll and get an action if child view visible on screen.
#SuppressLint("ClickableViewAccessibility")
fun View.setChildViewOnScreenListener(view: View, action: () -> Unit) {
val visibleScreen = Rect()
this.setOnTouchListener { _, motionEvent ->
if (motionEvent.action == MotionEvent.ACTION_MOVE) {
this.getDrawingRect(visibleScreen)
if (view.getLocalVisibleRect(visibleScreen)) {
action()
}
}
false
}
}
Use this extension function for any scrollable view
nestedScrollView.setChildViewOnScreenListener(childView) {
action()
}
You can use the FocusAwareScrollView which notifies when view becomes visible :
FocusAwareScrollView focusAwareScrollView = (FocusAwareScrollView) findViewById(R.id.focusAwareScrollView);
if (focusAwareScrollView != null) {
ArrayList<View> viewList = new ArrayList<>();
viewList.add(yourView1);
viewList.add(yourView2);
focusAwareScrollView.registerViewSeenCallBack(viewList, new FocusAwareScrollView.OnViewSeenListener() {
#Override
public void onViewSeen(View v, int percentageScrolled) {
if (v == yourView1) {
// user have seen view1
} else if (v == yourView2) {
// user have seen view2
}
}
});
}
Here is class :
import android.content.Context;
import android.graphics.Rect;
import android.support.v4.widget.NestedScrollView;
import android.util.AttributeSet;
import android.view.View;
import java.util.ArrayList;
import java.util.List;
public class FocusAwareScrollView extends NestedScrollView {
private List<OnScrollViewListener> onScrollViewListeners = new ArrayList<>();
public FocusAwareScrollView(Context context) {
super(context);
}
public FocusAwareScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public FocusAwareScrollView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public interface OnScrollViewListener {
void onScrollChanged(FocusAwareScrollView v, int l, int t, int oldl, int oldt);
}
public interface OnViewSeenListener {
void onViewSeen(View v, int percentageScrolled);
}
public void addOnScrollListener(OnScrollViewListener l) {
onScrollViewListeners.add(l);
}
public void removeOnScrollListener(OnScrollViewListener l) {
onScrollViewListeners.remove(l);
}
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
for (int i = onScrollViewListeners.size() - 1; i >= 0; i--) {
onScrollViewListeners.get(i).onScrollChanged(this, l, t, oldl, oldt);
}
super.onScrollChanged(l, t, oldl, oldt);
}
#Override
public void requestChildFocus(View child, View focused) {
super.requestChildFocus(child, focused);
}
private boolean handleViewSeenEvent(View view, int scrollBoundsBottom, int scrollYOffset,
float minSeenPercentage, OnViewSeenListener onViewSeenListener) {
int loc[] = new int[2];
view.getLocationOnScreen(loc);
int viewBottomPos = loc[1] - scrollYOffset + (int) (minSeenPercentage / 100 * view.getMeasuredHeight());
if (viewBottomPos <= scrollBoundsBottom) {
int scrollViewHeight = this.getChildAt(0).getHeight();
int viewPosition = this.getScrollY() + view.getScrollY() + view.getHeight();
int percentageSeen = (int) ((double) viewPosition / scrollViewHeight * 100);
onViewSeenListener.onViewSeen(view, percentageSeen);
return true;
}
return false;
}
public void registerViewSeenCallBack(final ArrayList<View> views, final OnViewSeenListener onViewSeenListener) {
final boolean[] viewSeen = new boolean[views.size()];
FocusAwareScrollView.this.postDelayed(new Runnable() {
#Override
public void run() {
final Rect scrollBounds = new Rect();
FocusAwareScrollView.this.getHitRect(scrollBounds);
final int loc[] = new int[2];
FocusAwareScrollView.this.getLocationOnScreen(loc);
FocusAwareScrollView.this.setOnScrollChangeListener(new NestedScrollView.OnScrollChangeListener() {
boolean allViewsSeen = true;
#Override
public void onScrollChange(NestedScrollView v, int x, int y, int oldx, int oldy) {
for (int index = 0; index < views.size(); index++) {
//Change this to adjust criteria
float viewSeenPercent = 1;
if (!viewSeen[index])
viewSeen[index] = handleViewSeenEvent(views.get(index), scrollBounds.bottom, loc[1], viewSeenPercent, onViewSeenListener);
if (!viewSeen[index])
allViewsSeen = false;
}
//Remove this if you want continuous callbacks
if (allViewsSeen)
FocusAwareScrollView.this.setOnScrollChangeListener((NestedScrollView.OnScrollChangeListener) null);
}
});
}
}, 500);
}
}
I ended up implementing a combination of two of the Java answers ( #bill-mote https://stackoverflow.com/a/12428154/3686125 and #denys-vasylenko https://stackoverflow.com/a/25528434/3686125 ) in my project as a set of Kotlin extensions, which support either standard vertial ScrollView or HorizontalScrollView controls.
I just tossed these in a Kotlin file named Extensions.kt, no class, just methods.
I used these to determine which item to snap to when a user stops scrolling in various scrollviews in my project:
fun View.isPartiallyOrFullyVisible(horizontalScrollView: HorizontalScrollView) : Boolean {
val scrollBounds = Rect()
horizontalScrollView.getHitRect(scrollBounds)
return getLocalVisibleRect(scrollBounds)
}
fun View.isPartiallyOrFullyVisible(scrollView: ScrollView) : Boolean {
val scrollBounds = Rect()
scrollView.getHitRect(scrollBounds)
return getLocalVisibleRect(scrollBounds)
}
fun View.isFullyVisible(horizontalScrollView: HorizontalScrollView) : Boolean {
val scrollBounds = Rect()
horizontalScrollView.getDrawingRect(scrollBounds)
val left = x
val right = left + width
return scrollBounds.left < left && scrollBounds.right > right
}
fun View.isFullyVisible(scrollView: ScrollView) : Boolean {
val scrollBounds = Rect()
scrollView.getDrawingRect(scrollBounds)
val top = y
val bottom = top + height
return scrollBounds.top < top && scrollBounds.bottom > bottom
}
fun View.isPartiallyVisible(horizontalScrollView: HorizontalScrollView) : Boolean = isPartiallyOrFullyVisible(horizontalScrollView) && !isFullyVisible(horizontalScrollView)
fun View.isPartiallyVisible(scrollView: ScrollView) : Boolean = isPartiallyOrFullyVisible(scrollView) && !isFullyVisible(scrollView)
Example usage, iterating through scrollview's LinearLayout children and logging outputs:
val linearLayoutChild: LinearLayout = getChildAt(0) as LinearLayout
val scrollView = findViewById(R.id.scroll_view) //Replace with your scrollview control or synthetic accessor
for (i in 0 until linearLayoutChild.childCount) {
with (linearLayoutChild.getChildAt(i)) {
Log.d("ScrollView", "child$i left=$left width=$width isPartiallyOrFullyVisible=${isPartiallyOrFullyVisible(scrollView)} isFullyVisible=${isFullyVisible(scrollView)} isPartiallyVisible=${isPartiallyVisible(scrollView)}")
}
}
My way:
scrollView.viewTreeObserver?.addOnScrollChangedListener {
scrollView.getDrawingRect(Rect())
myViewInsideScrollView.getLocalVisibleRect(Rect())
}
I know its very late. But i have a good solution. Below is the code snippet for getting view visibility percentage in scroll view.
First of all set touch listener on scroll view for getting callback for scroll stop.
#Override
public boolean onTouch(View v, MotionEvent event) {
switch ( event.getAction( ) ) {
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
if(mScrollView == null){
mScrollView = (ScrollView) findViewById(R.id.mScrollView);
}
int childCount = scrollViewRootChild.getChildCount();
//Scroll view location on screen
int[] scrollViewLocation = {0,0};
mScrollView.getLocationOnScreen(scrollViewLocation);
//Scroll view height
int scrollViewHeight = mScrollView.getHeight();
for (int i = 0; i < childCount; i++){
View child = scrollViewRootChild.getChildAt(i);
if(child != null && child.getVisibility() == View.VISIBLE){
int[] viewLocation = new int[2];
child.getLocationOnScreen(viewLocation);
int viewHeight = child.getHeight();
getViewVisibilityOnScrollStopped(scrollViewLocation, scrollViewHeight,
viewLocation, viewHeight, (String) child.getTag(), (childCount - (i+1)));
}
}
}
}, 150);
break;
}
return false;
}
In above code snippet, we are getting call backs for scroll view touch events and post a runnable after 150 millis(Not mandatory) after getting the callback for scroll stopped. In that runnable we will get location of scroll view on the screen and scroll view height. Then get the direct child viewgroup instance of scroll view and get the child counts. In my case direct child of scroll view is LinearLayout named scrollViewRootChild. Then iterate all the child views of scrollViewRootChild. In above code snippet you can see I am getting the location of the child on the screen in a integer array named viewLocation, get height of view in variable name viewHeight. Then i called a private method getViewVisibilityOnScrollStopped. You can get the understanding of the internal working of this method by reading documentation.
/**
* getViewVisibilityOnScrollStopped
* #param scrollViewLocation location of scroll view on screen
* #param scrollViewHeight height of scroll view
* #param viewLocation location of view on screen, you can use the method of view claas's getLocationOnScreen method.
* #param viewHeight height of view
* #param tag tag on view
* #param childPending number of views pending for iteration.
*/
void getViewVisibilityOnScrollStopped(int[] scrollViewLocation, int scrollViewHeight, int[] viewLocation, int viewHeight, String tag, int childPending) {
float visiblePercent = 0f;
int viewBottom = viewHeight + viewLocation[1]; //Get the bottom of view.
if(viewLocation[1] >= scrollViewLocation[1]) { //if view's top is inside the scroll view.
visiblePercent = 100;
int scrollBottom = scrollViewHeight + scrollViewLocation[1]; //Get the bottom of scroll view
if (viewBottom >= scrollBottom) { //If view's bottom is outside from scroll view
int visiblePart = scrollBottom - viewLocation[1]; //Find the visible part of view by subtracting view's top from scrollview's bottom
visiblePercent = (float) visiblePart / viewHeight * 100;
}
}else{ //if view's top is outside the scroll view.
if(viewBottom > scrollViewLocation[1]){ //if view's bottom is outside the scroll view
int visiblePart = viewBottom - scrollViewLocation[1]; //Find the visible part of view by subtracting scroll view's top from view's bottom
visiblePercent = (float) visiblePart / viewHeight * 100;
}
}
if(visiblePercent > 0f){
visibleWidgets.add(tag); //List of visible view.
}
if(childPending == 0){
//Do after iterating all children.
}
}
If you feel any improvement in this code please contribute.
Using #Qberticus answer which was to the point but great btw, I compined a bunch of codes to check if whenever a scrollview is called and got scrolled it trigger the #Qberticus answer and you can do whatever you want, in my case I have a social network containing videos so when the view is drawed on the screen I play the video same idea like facebook and Instagram.
Here's the code:
mainscrollview.getViewTreeObserver().addOnScrollChangedListener(new OnScrollChangedListener() {
#Override
public void onScrollChanged() {
//mainscrollview is my scrollview that have inside it a linearlayout containing many child views.
Rect bounds = new Rect();
for(int xx=1;xx<=postslayoutindex;xx++)
{
//postslayoutindex is the index of how many posts are read.
//postslayoutchild is the main layout for the posts.
if(postslayoutchild[xx]!=null){
postslayoutchild[xx].getHitRect(bounds);
Rect scrollBounds = new Rect();
mainscrollview.getDrawingRect(scrollBounds);
if(Rect.intersects(scrollBounds, bounds))
{
vidPreview[xx].startPlaywithoutstoppping();
//I made my own custom video player using textureview and initialized it globally in the class as an array so I can access it from anywhere.
}
else
{
}
}
}
}
});

Categories

Resources