StaggeredGridLayoutManager dislocation (2) - android

Same problem, don't know if this counts as a duplicate.
I have a problem with Android's StaggeredGridLayoutManager. The description of the problem is pretty much the same with StaggeredGridLayoutManager dislocation.
Here's the screenshot (sorry for the censorship, those are CardViews):
And the adapter:
public class ProductItemStaggeredAdapter extends RecyclerView.Adapter<ViewHolderRVProductItem> {
public ProductItemStaggeredAdapter(RealmResults<ProductItem> items, int theme) {
mItems = items;
this.theme = theme;
setHasStableIds(true);
}
#Override
public ViewHolderRVProductItem onCreateViewHolder(final ViewGroup parent, final int viewType) {
Context context = parent.getContext();
int blackGrey1 = ContextCompat.getColor(context, R.color.black_grey1);
int white = ContextCompat.getColor(context, R.color.white);
LayoutInflater inflater = LayoutInflater.from(context);
View view;
ViewHolderRVProductItem vh = null;
switch (viewType) {
case 100:
/** Error **/
view = inflater.inflate(R.layout.item_retry, parent, false);
StaggeredGridLayoutManager.LayoutParams lp3 = (StaggeredGridLayoutManager.LayoutParams) view.getLayoutParams();
lp3.setFullSpan(true);
view.setLayoutParams(lp3);
view.setOnClickListener(retryClickListener);
vh = new ViewHolderRVProductItem(view);
break;
case 101:
/** Loading **/
view = inflater.inflate(R.layout.item_loading, parent, false);
StaggeredGridLayoutManager.LayoutParams lp4 = (StaggeredGridLayoutManager.LayoutParams) view.getLayoutParams();
lp4.setFullSpan(true);
view.setLayoutParams(lp4);
vh = new ViewHolderRVProductItem(view);
break;
case Banner.ONE:
/**
* This banner occupies 1 column of the screen
* The height is set programmatically based on screen width
*
* Product image ratio is 32 x 15
*/
view = inflater.inflate(R.layout.item_banner_big, parent, false);
view.setOnClickListener(productClickListener);
StaggeredGridLayoutManager.LayoutParams lp = (StaggeredGridLayoutManager.LayoutParams) view.getLayoutParams();
lp.setFullSpan(true);
view.setLayoutParams(lp);
view.setOnClickListener(productClickListener);
vh = new ViewHolderRVProductItem(view);
break;
case Banner.TWO:
/**
* This banner occupies 1 column of the screen
* The height is smaller than BANNER_ONE
* The height is set programmatically based on screen width
*/
view = inflater.inflate(R.layout.item_banner_big, parent, false);
view.setOnClickListener(productClickListener);
StaggeredGridLayoutManager.LayoutParams lp2 = (StaggeredGridLayoutManager.LayoutParams) view.getLayoutParams();
lp2.setFullSpan(true);
view.setLayoutParams(lp2);
view.setOnClickListener(productClickListener);
vh = new ViewHolderRVProductItem(view);
break;
case Banner.THREE:
/**
* This banner occupies 3 column of the screen
* The height is set programmatically based on screen width
*/
view = inflater.inflate(R.layout.item_banner_long_rect, parent, false);
view.setOnClickListener(productClickListener);
vh = new ViewHolderRVProductItem(view);
break;
case Banner.FOUR:
/**
* This banner occupies 3 column of the screen
* The height is smaller than BANNER_THREE
* The height is set programmatically based on screen width
*/
view = inflater.inflate(R.layout.item_banner_long_rect, parent, false);
view.setOnClickListener(productClickListener);
vh = new ViewHolderRVProductItem(view);
break;
default: /** ProductItem.BANNER_FIVE **/
/**
* This banner occupies 2 column of the screen
* The height is set programmatically based on screen width
*/
view = inflater.inflate(R.layout.item_banner_recommended, parent, false);
view.setOnClickListener(productClickListener);
vh = new ViewHolderRVProductItem(view);
break;
}
/** Set image ratio **/
if (vh.productImage != null) {
final ViewHolderRVProductItem finalVh = vh;
vh.itemView.post(new Runnable() {
#Override
public void run() {
setImageRatio(finalVh.productImage, viewType, finalVh.productImage.getWidth());
}
});
}
return vh;
}
#Override
public void onBindViewHolder(final ViewHolderRVProductItem holder, final int position) {
if (holder == null) return;
final Context context = holder.itemView.getContext();
final ProductItem item = mItems.get(position);
holder.itemView.setTag(item);
final int bannerType = holder.getItemViewType();
final Banner banner = bannerType == Banner.ONE ? item.getBanner1() : (bannerType == Banner.FIVE ? item.getBanner5() : (bannerType == Banner.TWO ? item.getBanner2() : (bannerType == Banner.THREE ? item.getBanner3() : item.getBanner4())));
if (banner != null && holder.productImage != null) {
holder.productImage.post(new Runnable() {
#Override
public void run() {
float width = holder.productImage.getWidth();
float height = holder.productImage.getHeight();
Picasso.with(context)
.load(banner.getImageURL6())
.resize((int) width, (int) height)
.into(holder.productImage);
}
});
}
}
private void setImageRatio(ImageView imgview, int bannerType, int width) {
Context context = imgview.getContext();
switch (bannerType) {
case Banner.ONE:
/** Product image ratio is 32 x 15 **/
float height = (15f / 32f) * width;
LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) imgview.getLayoutParams();
params.width = width;
params.height = (int) height;
imgview.setLayoutParams(params);
break;
case Banner.TWO:
/** Product image ratio is 11 x 3 **/
float height1 = (3f / 11f) * width;
LinearLayout.LayoutParams params1 = (LinearLayout.LayoutParams) imgview.getLayoutParams();
params1.width = width;
params1.height = (int) height1;
imgview.setLayoutParams(params1);
break;
case Banner.THREE:
/** Product image ratio: 27 x 40 **/
float height2 = (40f / 27f) * width;
RelativeLayout.LayoutParams params2 = (RelativeLayout.LayoutParams) imgview.getLayoutParams();
params2.width = (int) width;
params2.height = (int) height2;
imgview.setLayoutParams(params2);
break;
case Banner.FOUR:
int dip8 = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 8f, context.getResources().getDisplayMetrics());
/** Product image ratio: 1 x 1 **/
int size = (int) width - dip8;
RelativeLayout.LayoutParams params3 = (RelativeLayout.LayoutParams) imgview.getLayoutParams();
params3.width = size;
params3.height = size;
params3.leftMargin = dip8;
params3.topMargin = dip8;
params3.rightMargin = dip8;
imgview.setLayoutParams(params3);
break;
case Banner.FIVE:
/** Product image ratio: 162 x 71 **/
float height3 = (71f / 162f) * width;
RelativeLayout.LayoutParams params4 = (RelativeLayout.LayoutParams) imgview.getLayoutParams();
params4.width = (int) width;
params4.height = (int) height3;
imgview.setLayoutParams(params4);
break;
}
}
}
The thing that the previous problem and this problem have done is changing the ImageView width & height programmatically. Why changing View's layout have impact in RecyclerView?

I've found an ultimate solution. Rather than using previous solution, i have found the culprit:
final ViewHolderRVProductItem finalVh = vh;
vh.itemView.post(new Runnable() {
#Override
public void run() {
setImageRatio(finalVh.productImage, viewType, finalVh.productImage.getWidth());
}
});
I think it's not recommended to modify ImageView's size using post inside an adapter. I'm now using AspectRatioImageView and it's working smoothly.

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!

Android - ItemDecoration sometimes gone or misplaced while adding item or scrolling

I have just started using ItemDecoration. I am trying to achieve a divider in the middle of a two-column RecyclerView managed by StaggeredGridLayoutManager.
Here's my ItemDecoration code :
public class LobbyItemDecoration extends RecyclerView.ItemDecoration {
private int margin;
private Drawable drawable;
public LobbyItemDecoration(int margin, Drawable drawable){
this.margin = margin;
this.drawable = drawable;
}
#Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
int position = parent.getChildAdapterPosition(view);
StaggeredGridLayoutManager.LayoutParams lp = (StaggeredGridLayoutManager.LayoutParams)view .getLayoutParams();
int spanIndex = lp.getSpanIndex();
if(position >= 0){
if (position < ((LobiAdapter)parent.getAdapter()).getmDataset().size()){
if(spanIndex == 1){
outRect.left = margin;
((LobiAdapter)parent.getAdapter()).getmDataset().get(position).left = false;
} else {
outRect.right = margin;
((LobiAdapter)parent.getAdapter()).getmDataset().get(position).left = true;
}
outRect.bottom = margin;
}
}
}
#Override
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
if (drawable == null) { super.onDrawOver(c, parent, state);return; }
if(parent.getItemAnimator() != null && parent.getItemAnimator().isRunning()) {
return;
}
final int top = parent.getPaddingTop();
final int bottom = parent.getHeight() - parent.getPaddingBottom();
final int childCount = parent.getChildCount();
for (int i=1; i < childCount; i++) {
final View child = parent.getChildAt(i);
StaggeredGridLayoutManager.LayoutParams lp = (StaggeredGridLayoutManager.LayoutParams)child .getLayoutParams();
int spanIndex = lp.getSpanIndex();
int size = drawable.getIntrinsicWidth();
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
final int ty = (int)(child.getTranslationY() + 0.5f);
final int tx = (int)(child.getTranslationX() + 0.5f);
final int leftIndex1 = child.getLeft() - (margin * 4 / 3) - tx;
final int rightIndex1 = child.getLeft() - (margin * 2 / 3) - tx;
final int leftIndex0 = child.getRight() + (margin * 2 / 3) + tx;
final int rightIndex0 = child.getRight() + (margin * 4 / 3) + tx;
if(spanIndex == 1){
// drawable.setBounds(100, top, 5, bottom);
drawable.setBounds(leftIndex1, top, rightIndex1, bottom);
drawable.draw(c);
} else {
drawable.setBounds(leftIndex0, top, rightIndex0, bottom);
drawable.draw(c);
// drawable.setBounds(5, top, 100, bottom);
}
}
}
}
Problem is as title said, whenever I load new item or scroll, the decoration is sometimes gone or misplaced. By gone I mean, literally gone, it's not there anymore until I scroll down or up to recycle the View.
And by misplaced I mean, when I scroll down, and the loading ViewHolder is in the left column, the divider "sticks" to the left column. If the loading ViewHolder is in the right column, it is normal.
Just in case : I use a ViewHolder to be a progress indicator by adding one null item and check it in getItemViewType() then remove it later after finishing the load from server.
This is how I add :
for (PostModel postModel :
dataSet) {
this.mDataset.add(postModel);
notifyItemInserted(mDataset.size() - 1);
}
StaggeredGridLayoutManager is buggy. For me this helped:
diffResult.dispatchUpdatesTo(this);
recyclerView.postDelayed(() -> {
layoutManager.invalidateSpanAssignments(); // to fix first item in second column issue
recyclerView.invalidateItemDecorations(); // to fix wrong spacing
}, 50);

Custom Linear Layout Manager add an empty card to recyclerView

I am using recycler view to display list of cards in my android app. Earlier i was using LinearLayoutManager to set layout manager for recycler view. But using this wrapcontent value for height tag of recycler view was not working and it covered whole app screen.
Using #davkutalek answer in this question How do I make WRAP_CONTENT work on a RecyclerView i was able to set recyclerview height to wrap content
This is my CustomLinearLayoutManager Class
public class CustomLinearLayoutManager extends LinearLayoutManager {
public CustomLinearLayoutManager(Context context) {
super(context);
}
private int[] mMeasuredDimension = new int[2];
#Override
public void onMeasure(RecyclerView.Recycler recycler, RecyclerView.State state, int widthSpec, int heightSpec) {
final int widthMode = View.MeasureSpec.getMode(widthSpec);
final int heightMode = View.MeasureSpec.getMode(heightSpec);
final int widthSize = View.MeasureSpec.getSize(widthSpec);
final int heightSize = View.MeasureSpec.getSize(heightSpec);
int width = 0;
int height = 0;
for (int i = 0; i < getItemCount() ; i++) {
measureScrapChild(recycler, i,
widthSpec,
View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED),
mMeasuredDimension);
if (getOrientation() == HORIZONTAL) {
width = width + mMeasuredDimension[0];
if (i == 0) {
height = mMeasuredDimension[1];
}
} else {
height = height + mMeasuredDimension[1];
if (i == 0) {
width = mMeasuredDimension[0];
}
}
}
switch (widthMode) {
case View.MeasureSpec.EXACTLY:
width = widthSize;
case View.MeasureSpec.AT_MOST:
case View.MeasureSpec.UNSPECIFIED:
}
switch (heightMode) {
case View.MeasureSpec.EXACTLY:
height = heightSize;
case View.MeasureSpec.AT_MOST:
case View.MeasureSpec.UNSPECIFIED:
}
setMeasuredDimension(width, height);
}
private void measureScrapChild(RecyclerView.Recycler recycler, int position, int widthSpec,
int heightSpec, int[] measuredDimension) {
View view = recycler.getViewForPosition(position);
// For adding Item Decor Insets to view
super.measureChildWithMargins(view, 0, 0);
if (view != null) {
RecyclerView.LayoutParams p = (RecyclerView.LayoutParams) view.getLayoutParams();
int childWidthSpec = ViewGroup.getChildMeasureSpec(widthSpec,
getPaddingLeft() + getPaddingRight() + getDecoratedLeft(view) + getDecoratedRight(view), p.width);
int childHeightSpec = ViewGroup.getChildMeasureSpec(heightSpec,
getPaddingTop() + getPaddingBottom() + getPaddingBottom() + getDecoratedBottom(view), p.height);
view.measure(childWidthSpec, childHeightSpec);
// Get decorated measurements
measuredDimension[0] = getDecoratedMeasuredWidth(view) + p.leftMargin + p.rightMargin;
measuredDimension[1] = getDecoratedMeasuredHeight(view) + p.bottomMargin + p.topMargin;
recycler.recycleView(view);
}
}
}
Using this class, a empty card is always shown even if i havent populated my recycler view with any data
If i use some data to populate recycler view then this empty card is shown at the end of all cards.
Screenshot 1:
Screenshot 2:
Using Logs i found out onMeasure() method is called twice with same data.
This is my Fragment which uses recyclerview
private ContentTestResult contentTestResult;
private RVAdapter rvAdapter;
private RecyclerView recyclerView;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View view = inflater.inflate(R.layout.fragment_test_result, container, false);
contentTestResult = new ContentTestResult();
recyclerView = (RecyclerView) view.findViewById(R.id.recyclerView_card_test);
recyclerView.setHasFixedSize(false);
// LinearLayoutManager linearLayoutManager = new LinearLayoutManager(getActivity());
CustomLinearLayoutManager linearLayoutManager = new CustomLinearLayoutManager(getActivity());
recyclerView.setLayoutManager(linearLayoutManager);
contentTestResult.clear();
// contentTestResult.addItem(new ContentTestResult.DummyItem("ABC", "XYZ", 1));
// contentTestResult.addItem(new ContentTestResult.DummyItem("EFG", "MNO", 2));
rvAdapter=new RVAdapter(contentTestResult.ITEMS);
recyclerView.setAdapter(rvAdapter);
return view;
}
private class RVAdapter extends RecyclerView.Adapter<RVAdapter.CardViewHolder> {
ContentTestResult content = new ContentTestResult();
public RVAdapter ( List<ContentTestResult.DummyItem> list_dummy ){
content.ITEMS = list_dummy;
}
#Override
public RVAdapter.CardViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.card_test_result, parent, false );
CardViewHolder cardViewHolder = new CardViewHolder( view );
return cardViewHolder;
}
#Override
public void onBindViewHolder(RVAdapter.CardViewHolder holder, int position) {
holder.place.setText(content.ITEMS.get(position).place);
holder.near.setText("Near " + content.ITEMS.get(position).near);
holder.price.setText("No. " + content.ITEMS.get(position).price);
}
#Override
public int getItemCount() {
return content.ITEMS.size();
}
public class CardViewHolder extends RecyclerView.ViewHolder {
CardView cardView;
TextView place, near, price;
public CardViewHolder(View itemView) {
super(itemView);
cardView = (CardView) itemView.findViewById(R.id.cardView_card_test);
place = (TextView) itemView.findViewById(R.id.text_card_test_place);
near = (TextView) itemView.findViewById(R.id.text_card_test_near);
price = (TextView) itemView.findViewById(R.id.text_card_test_price);
}
}
}
I searched for possible reasons but couldnt find any.
Any explanation and suggestion is appreciated.

Get location on screen for view in view holder

I have a Recyclerview that is managed by an adapter. For each item in the recycler I inflate a complex view that contains also a horizontal progressbar which takes the whole width of screen.
I have to position a baloon TextView with the percentage value (20% , 60% etc) that points to the progressbar like an indicator to the amount of progress. I tried using this code
int[] startPosition = new int[2];
Integer progresswidth;
WindowManager wm = (WindowManager) contextthis.getSystemService(Context.WINDOW_SERVICE);
Display display = wm.getDefaultDisplay();
Point size = new Point();
display.getSize(size);
progresswidth = size.x;
holder.horiz_progress.getLocationOnScreen(startPosition);
float exactvalue = (progresswidth * currentitem.getMatchPercent()) / 100;
startPosition[0] = startPosition[0] + Math.round(exactvalue);
startPosition[0] = startPosition[0] - holder.baloon_txt.getWidth() / 3 ;
startPosition[1] = startPosition[1] + 10;
holder.baloon_txt.setX(startPosition[0]);
holder.baloon_txt.setY(startPosition[1]);
But the problem is that holder.horiz_progress.getLocationOnScreen always returns 0 so I cannot position the balloon_txt.
I had a similar issue inside an activity and there i resolved it overriding OnWindowFocusChanged but this is inside the adapter so I don't know how to get it done.
EDIT
My current adapter code:
public class ResultsAdapter extends RecyclerView.Adapter<ResultsAdapter.ViewHolder>{
private List<ResultsItem> mResults;
private View mSelectedView;
private int mSelectedPosition;
Context contextthis;
android.os.Handler handler;
int[] startPosition = new int[2];
public ResultsAdapter(Context context,List<ResultsItem> resultsItemList) {
this.mResults = resultsItemList;
contextthis = context;
}
#Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.results_item, null);
final ViewHolder viewHolder = new ViewHolder(v);
viewHolder.results_likeimg.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
}
});
return viewHolder;
}
#Override
public void onBindViewHolder( final ViewHolder holder, int position) {
final ResultsItem currentitem = mResults.get(position);
//set Image
if(currentitem.getImageslist().get(0).getPicture() != null)
ImageLoader.getInstance().displayImage(currentitem.getImageslist().get(0).getPicture(), holder.results_img);
//baloon set
holder.baloon_txt.setText(currentitem.getMatchPercent() + "% " + "Match");
holder.horiz_progress.setProgress(currentitem.getMatchPercent());
final View view = holder.horiz_progress;
view.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
view.getLocationOnScreen(startPosition);
Integer progresswidth;
WindowManager wm = (WindowManager) contextthis.getSystemService(Context.WINDOW_SERVICE);
Display display = wm.getDefaultDisplay();
Point size = new Point();
display.getSize(size);
progresswidth = size.x;
float exactvalue = (progresswidth * currentitem.getMatchPercent()) / 100;
startPosition[0] = startPosition[0] + Math.round(exactvalue);
startPosition[0] = startPosition[0] - holder.baloon_txt.getWidth() / 3 ;
startPosition[1] = startPosition[1] + 10;
holder.baloon_txt.setX(startPosition[0]);
holder.baloon_txt.setY(startPosition[1]);
}
});
//logo
if(currentitem.getPriceslist().get(0).getSource() != null)
ImageLoader.getInstance().displayImage(currentitem.getPriceslist().get(0).getSource(), holder.results_logo_img);
//description
holder.description_txt.setText(currentitem.getDescription());
//price
holder.price_curr.setText(currentitem.getPriceslist().get(0).getCurrency());
holder.price_txt.setText(String.valueOf(currentitem.getPriceslist().get(0).getPrice()));
}
#Override
public int getItemCount() {
return mResults.size();
}
public static class ViewHolder extends RecyclerView.ViewHolder {
ImageView results_img, results_dislikeimg, results_likeimg, results_logo_img;
ProgressBar horiz_progress;
TextView baloon_txt, price_txt, description_txt, buybtn, sharebtn, price_curr;
public ViewHolder(View view) {
super(view);
this.results_img = (ImageView) view.findViewById(R.id.resultsitem_img);
this.results_dislikeimg = (ImageView) view.findViewById(R.id.results_item_dislike);
this.results_likeimg = (ImageView) view.findViewById(R.id.resultsitem_like);
this.results_logo_img = (ImageView) view.findViewById(R.id.logoimg);
this.horiz_progress = (ProgressBar) view.findViewById(R.id.progressBar_horizontal);
this.baloon_txt = (TextView) view.findViewById(R.id.baloonMatch_txt);
this.price_txt = (TextView) view.findViewById(R.id.price_txt);
this.description_txt = (TextView) view.findViewById(R.id.description_txt);
this.buybtn = (TextView) view.findViewById(R.id.buybtn);
this.sharebtn = (TextView) view.findViewById(R.id.sharebtn);
this.price_curr = (TextView) view.findViewById(R.id.price_curr);
}
}
}
getLocation() returns 0 because the view has not been laid out yet. You need to set a layout listener:
final View view = holder.horiz_progress;
view.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
// Do what you need to do here.
// Then remove the listener:
view.getViewTreeObserver().removeOnGlobalLayoutListener(this);
}
});

Twoway view spannable grid layout parameters not working

I'm using the twoway view for the first and i'm struggling to specify the layout attributes for the views. I have tow kinds of views, header and the episode.
This is the xml of both:
Show:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_margin="#dimen/margin_16dp"
android:layout_width="248dp"
android:layout_height="160dp"
android:background="#color/black">
//More views...
</RelativeLayout>
Header:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="54dp"
android:background="#color/red_800">
//More views
I set the height to be 54dp in the header and 160dp for the show but what i'm getting is this, it's always the same (wrong) height, the red bar is the header.
http://postimg.org/image/xx1dk45n9/
Code:
public static class ViewHolder extends RecyclerView.ViewHolder{
public TextView title,date,rate,episode;
public ImageView image;
public ViewHolder(View view) {
super(view);
image = (ImageView)view.findViewById(R.id.image);
title = (TextView)view.findViewById(R.id.title);
date = (TextView)view.findViewById(R.id.date);
rate = (TextView)view.findViewById(R.id.rate);
episode = (TextView)view.findViewById(R.id.episode);
}
}
public static class ViewHolderHeader extends RecyclerView.ViewHolder{
public TextView date,children;
public ViewHolderHeader(View view) {
super(view);
date = (TextView)view.findViewById(R.id.date);
children = (TextView)view.findViewById(R.id.children);
}
}
public CalendarAdapter(Context context) {
mContext = context;
readDateFormat(context);
simpleDateFormat = new SimpleDateFormat(datePattern, Locale.US);
}
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
final View view;
if (viewType == CalendarGridItem.TYPE.SHOW.ordinal()){
view = LayoutInflater.from(mContext).inflate(R.layout.calendar_grid_tile, parent, false);
return new ViewHolder(view);
}
else {
view = LayoutInflater.from(mContext).inflate(R.layout.calendar_grid_header, parent, false);
return new ViewHolderHeader(view);
}
}
#Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
final View view = holder.itemView;
final CalendarGridItem item = mShows.get(position);
final SpannableGridLayoutManager.LayoutParams lp =
(SpannableGridLayoutManager.LayoutParams) view.getLayoutParams();
if (getItemViewType(position) == CalendarGridItem.TYPE.HEADER.ordinal()){ //Header
final ViewHolderHeader holder1 = (ViewHolderHeader)holder;
CalendarHeader header = (CalendarHeader)item;
Log.d(TAG,"Entrou header: "+header.getDate());
//Mete a colSpan a 2
if (lp.colSpan != 2)
lp.colSpan = 2;
lp.height = 100; //Even forcing the height doesn't work
view.setLayoutParams(lp);
}else{ //Show
final ViewHolder holder1 = (ViewHolder)holder;
UpcomingShow show = (UpcomingShow)item;
if (lp.colSpan != 1)
lp.colSpan = 1;
view.setLayoutParams(lp);
}
}
Fragment layout:
<org.lucasr.twowayview.widget.TwoWayView
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/spanGrid"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/background"
style="#style/TwoWayView"
app:twowayview_layoutManager="SpannableGridLayoutManager"
app:twowayview_numColumns="2"/>
What i'm doing wrong ? I'm not used to the recycler view way of doing things, i must be missing something. Teste with Android 4.1.1 and 4.4
Currently have quite the same problem (mine is that elements are all "squares")
It seems like it not possible to give specific height/width for elements in the item layout.
When you look in the source code, in the SpannableGridLayoutManager.java file, you have the method : LayoutParams generateLayoutParams(ViewGroup.LayoutParams lp).
In there, those are the incrimated lines :
final LayoutParams spannableLp = new LayoutParams((MarginLayoutParams) lp);
spannableLp.width = LayoutParams.MATCH_PARENT;
spannableLp.height = LayoutParams.MATCH_PARENT;
But if you do remove that, you "break" all the logic for the spannable grid view...
I also thought that TwoWayView can display the items as squares. So I was digging in the source and found , like Wicha said, that MATCH_PARENT in two places was causing the undesired behaviour.
So I made the following adjustments in the SpannableGridLayoutManager file (commented some lines in checkLayoutParams method) :
#Override
public boolean checkLayoutParams(RecyclerView.LayoutParams lp) {
// if (lp.width != LayoutParams.MATCH_PARENT ||
// lp.height != LayoutParams.MATCH_PARENT) {
// return false;
// }
if (lp instanceof LayoutParams) {
(.....)
}
and here (use width and height from layout params in generateLayoutParams method) :
#Override
public LayoutParams generateLayoutParams(ViewGroup.LayoutParams lp) {
final LayoutParams spannableLp = new LayoutParams((MarginLayoutParams) lp);
spannableLp.width = lp.width; //LayoutParams.MATCH_PARENT;
spannableLp.height = lp.height; //LayoutParams.MATCH_PARENT;
if (lp instanceof LayoutParams) {
(....)
}
Also, in your Adapter class, goes something like this :
#Override
public void onBindViewHolder(ViewHolder viewHolder, final int i) {
viewHolder.image.setScaleType(ImageView.ScaleType.FIT_XY);
viewHolder.image.setPadding(5,5,5,5);
SpannableGridLayoutManager.LayoutParams layoutParams = ((SpannableGridLayoutManager.LayoutParams) viewHolder.itemView.getLayoutParams());
layoutParams.width = cell_width * item.size; //multiply size
layoutParams.height = cell_height * item.size;//multiply size
layoutParams.colSpan = item.size;
layoutParams.rowSpan = item.size;
viewHolder.image.setLayoutParams(layoutParams);
}
Some observations:
cell_width is calculated like screen width/3 in portrait orientation and screen width/4 in landscape.
cell_height is calculated like image_width / image_ratio.
item.size : I decided to have 1:1 or 2:2 or 3:3 aspect ratios for the items, so item_size is the multiplication value.

Categories

Resources