How to create a rectangle shape dynamically using float values? - android

I want to dynamically create rectangle shape by using float values from server. The shapes should be precise like if any value is 25.2 and another is 25.3 then the 25.3 one should look bigger like we see in charts. So is there any way to achieve this? Here's the image:
I was trying to change the view size by using this:
itemView.tv_value.layoutParams = LinearLayout.LayoutParams(width,height)
But this seems to be accepting integer values only and if float converted to int using double then it will round off to the nearest number and it won't work.
How to achieve this either by using canvas or views?

First of all width or height of any view can not be float value.
You can set int value based on pixel of screen and float value ratio.
Layout for adapter to generate graph...
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="50dp"
android:layout_marginLeft="5dp"
android:orientation="horizontal">
<LinearLayout
android:layout_width="0dp"
android:layout_weight="0.8"
android:layout_height="wrap_content"
android:minHeight="50dp"
android:orientation="vertical"
android:gravity="center_vertical|center_horizontal|left"
android:id="#+id/lo_dynamic_view_container">
</LinearLayout>
<TextView
android:id="#+id/tv_chart_value"
android:layout_width="0dp"
android:layout_weight="0.2"
android:minHeight="50dp"
android:textColor="#000"
android:gravity="center_horizontal|center_vertical"
android:layout_height="match_parent"/>
</LinearLayout>
Generate Ration
private float getRatio(int width, float value, float highestValue){
float result = 0;
result = ( (float)width/highestValue) * value;
Log.e("Result", "width: "+ width +" "+(int) Math.floor(result)+"");
return result;
}
Combine Activity and adapter calss
public class MainActivity extends AppCompatActivity {
RecyclerView recyclerView;
ArrayList<Data> listData = new ArrayList<>();
BarChartAdapter barChartAdapter;
int[] colors = {Color.GREEN, Color.CYAN, Color.MAGENTA, Color.RED };
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
recyclerView = findViewById(R.id.rv_bar_chart);
listData.add(new Data(8.0f,Color.GREEN));
listData.add(new Data(4.0f,Color.CYAN));
listData.add(new Data(2.0f,Color.MAGENTA));
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
recyclerView.setLayoutManager(layoutManager);
barChartAdapter = new BarChartAdapter(listData);
recyclerView.setAdapter(barChartAdapter);
}
public class BarChartAdapter extends RecyclerView.Adapter<BarChartAdapter.MyViewHolder> {
ArrayList<Data> listData = new ArrayList<>();
public class MyViewHolder extends RecyclerView.ViewHolder {
public TextView textView;
LinearLayout layout;
public MyViewHolder(View v) {
super(v);
textView = v.findViewById(R.id.tv_chart_value);
layout = (LinearLayout) v.findViewById(R.id.lo_dynamic_view_container);
}
}
public BarChartAdapter(ArrayList<Data> listData) {
this.listData = listData;
}
#Override
public BarChartAdapter.MyViewHolder onCreateViewHolder(ViewGroup parent,int viewType) {
View v = (View) LayoutInflater
.from(parent.getContext())
.inflate(R.layout.barchart_layout, parent, false);
MyViewHolder vh = new MyViewHolder(v);
return vh;
}
#Override
public void onBindViewHolder(MyViewHolder holder, int position) {
holder.setIsRecyclable(false);
holder.textView.setText(listData.get(position).value+"");
Display display = getWindowManager().getDefaultDisplay();
int width = display.getWidth();
width = width - (100/width)*80;
LinearLayout.LayoutParams lparams = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT);
TextView tv = new TextView(MainActivity.this);
tv.setLayoutParams(lparams);
tv.setWidth((int) Math.floor( getRatio(width, listData.get(position).value,getHighestValue(listData))));
float redious [] = { 0, 0, 8.3f, 8.5f, 8.2f, 8.9f, 0, 0 };
ShapeDrawable shape = new ShapeDrawable (new RoundRectShape(redious,null,null));
shape.getPaint().setColor(listData.get(position).color);
//shape.getPaint().setColor(colors[new Random().nextInt((colors.length-1) - 0 + 1) + 0]);
//shape.getPaint().setColor(Color.GREEN);
tv.setBackground(shape);
holder.layout.addView(tv);
}
#Override
public int getItemCount() {
return listData.size();
}
}
private float getRatio(int width, float value, float highestValue){
float result = 0;
result = ( (float)width/highestValue) * value;
Log.e("Result", "width: "+ width +" "+(int) Math.floor(result)+"");
return result;
}
private float getHighestValue(ArrayList<Data> listData){
float result = 0.0f;
if(listData!=null){
if(listData.size()>0){
for (int i = 0; i < listData.size(); i++) {
result = listData.get(i).value>result?listData.get(i).value:result;
}
}
}
return result;
}
class Data{
float value;
int color;
public Data(float value, int color) {
this.value = value;
this.color = color;
}
}
}
Screen Shoot
Full Project link on GitHub

Related

slowly scroll in page

I want to make a view slowly scrolls into mobile page, first like this:
then like this:
the view with red background slowly scrolls into mobile page.
Here is the xml file:
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="#+id/iv_clean_complete"
android:layout_width="140dp"
android:layout_height="140dp"
android:layout_marginTop="80dp"
android:src="#mipmap/img_clean_completed_ok"
android:layout_centerHorizontal="true"/>
<RelativeLayout
android:id="#+id/layout_btm"
android:background="#color/colorAccent"
android:layout_width="match_parent"
android:layout_height="288dp">
<TextView
android:text="Hello World"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
</TextView>
</RelativeLayout>
</RelativeLayout>
</ScrollView>
Here is the code:
public class MainActivity extends AppCompatActivity {
private RelativeLayout layoutBtm;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
layoutBtm = findViewById(R.id.layout_btm);
int topMargin = getHeightPixels();
ViewGroup.MarginLayoutParams marginLayoutParams =
new ViewGroup.MarginLayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, dip2px(this, 288));
marginLayoutParams.topMargin = topMargin;
RelativeLayout.LayoutParams layoutParams2 = new RelativeLayout.LayoutParams(marginLayoutParams);
layoutParams2.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
layoutBtm.setLayoutParams(layoutParams2);
ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(layoutBtm, "translationY", -dip2px(this, 288));
objectAnimator.setDuration(1000);
objectAnimator.start();
}
private int getHeightPixels(){
DisplayMetrics dm = new DisplayMetrics();
dm = getResources().getDisplayMetrics();
return dm.heightPixels;
}
private int getStatusBarHeight(Activity activity){
try {
Class<?> clazz = Class.forName("com.android.internal.R$dimen");
Object object = clazz.newInstance();
Field field = clazz.getField("status_bar_height");
int dpHeight = Integer.parseInt(field.get(object).toString());
return activity.getResources().getDimensionPixelSize(dpHeight);
} catch (Exception e) {
return 0;
}
}
private int dip2px(Context context, float dipValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (dipValue * scale + 0.5f);
}
}
It seems to be fine. But after the red view scrolls in, there is some space left at the bottom.The page doesn't change its height automatically
Finally I know how to solve this problem. I edit the code of change the translationY of the view to change the top margin of the view.Here is the code after change:
public class MainActivity extends AppCompatActivity {
private RelativeLayout layoutBtm;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
layoutBtm = findViewById(R.id.layout_btm);
int topMargin = getHeightPixels();
ViewGroup.MarginLayoutParams marginLayoutParams =
new ViewGroup.MarginLayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, dip2px(this, 288));
marginLayoutParams.topMargin = topMargin;
RelativeLayout.LayoutParams layoutParams2 = new RelativeLayout.LayoutParams(marginLayoutParams);
layoutParams2.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
layoutBtm.setLayoutParams(layoutParams2);
ValueAnimator animator = ValueAnimator.ofInt(getHeightPixels(), getHeightPixels() - getStatusBarHeight(this) -dip2px(this, 288));
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator animator) {
int currentValue = (int) animator.getAnimatedValue();
ViewGroup.MarginLayoutParams marginLayoutParams =
new ViewGroup.MarginLayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, dip2px(MainActivity.this, 288));
marginLayoutParams.topMargin = currentValue;
RelativeLayout.LayoutParams layoutParams2 = new RelativeLayout.LayoutParams(marginLayoutParams);
layoutParams2.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
layoutBtm.setLayoutParams(layoutParams2);
layoutBtm.requestLayout();
}
});
animator.setDuration(1000);
animator.start();
}
private int getHeightPixels(){
DisplayMetrics dm = new DisplayMetrics();
dm = getResources().getDisplayMetrics();
return dm.heightPixels;
}
private int getStatusBarHeight(Activity activity){
try {
Class<?> clazz = Class.forName("com.android.internal.R$dimen");
Object object = clazz.newInstance();
Field field = clazz.getField("status_bar_height");
int dpHeight = Integer.parseInt(field.get(object).toString());
return activity.getResources().getDimensionPixelSize(dpHeight);
} catch (Exception e) {
return 0;
}
}
private int dip2px(Context context, float dipValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (dipValue * scale + 0.5f);
}
}

How to hide divider when delete animation happens in recycler view

RecyclerView by default, does come with a nice deletion animation, as long as you setHasStableIds(true) and provide correct implementation on getItemId.
Recently, I had added divider into RecyclerView via https://stackoverflow.com/a/27037230/72437
The outcome looks as following
https://www.youtube.com/watch?v=u-2kPZwF_0w
https://youtu.be/c81OsFAL3zY (To make the dividers more visible when delete animation played, I temporary change the RecyclerView background to red)
The dividers are still visible, when deletion animation being played.
However, if I look at GMail example, when deletion animation being played, divider lines are no longer visible. They are being covered a solid color area.
https://www.youtube.com/watch?v=cLs7paU-BIg
May I know, how can I achieve the same effect as GMail, by not showing divider lines, when deletion animation played?
The solution is fairly easy. To animate a decoration, you can and should use view.getTranslation_() and view.getAlpha(). I wrote a blog post some time ago on this exact issue, you can read it here.
Translation and fading off
The default layout manager will fade views out (alpha) and translate them, when they get added or removed. You have to account for this in your decoration.
The idea is simple:
However you draw your decoration, apply the same alpha and translation to your drawing by using view.getAlpha() and view.getTranslationY().
Following your linked answer, it would have to be adapted like the following:
// translate
int top = child.getBottom() + params.bottomMargin + view.getTranslationY();
int bottom = top + mDivider.getIntrinsicHeight();
// apply alpha
mDivider.setAlpha((int) child.getAlpha() * 255f);
mDivider.setBounds(left + view.getTranslationX(), top,
right + view.getTranslationX(), bottom);
mDivider.draw(c);
A complete sample
I like to draw things myself, since I think drawing a line is less overhead than layouting a drawable, this would look like the following:
public class SeparatorDecoration extends RecyclerView.ItemDecoration {
private final Paint mPaint;
private final int mAlpha;
public SeparatorDecoration(#ColorInt int color, float width) {
mPaint = new Paint();
mPaint.setColor(color);
mPaint.setStrokeWidth(width);
mAlpha = mPaint.getAlpha();
}
#Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) view.getLayoutParams();
// we retrieve the position in the list
final int position = params.getViewAdapterPosition();
// add space for the separator to the bottom of every view but the last one
if (position < state.getItemCount()) {
outRect.set(0, 0, 0, (int) mPaint.getStrokeWidth()); // left, top, right, bottom
} else {
outRect.setEmpty(); // 0, 0, 0, 0
}
}
#Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
// a line will draw half its size to top and bottom,
// hence the offset to place it correctly
final int offset = (int) (mPaint.getStrokeWidth() / 2);
// this will iterate over every visible view
for (int i = 0; i < parent.getChildCount(); i++) {
final View view = parent.getChildAt(i);
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) view.getLayoutParams();
// get the position
final int position = params.getViewAdapterPosition();
// and finally draw the separator
if (position < state.getItemCount()) {
// apply alpha to support animations
mPaint.setAlpha((int) (view.getAlpha() * mAlpha));
float positionY = view.getBottom() + offset + view.getTranslationY();
// do the drawing
c.drawLine(view.getLeft() + view.getTranslationX(),
positionY,
view.getRight() + view.getTranslationX(),
positionY,
mPaint);
}
}
}
}
Firstly, sorry for the massive answer size. However, I felt it necessary to include my entire test Activity so that you can see what I have done.
The issue
The issue that you have, is that the DividerItemDecoration has no idea of the state of your row. It does not know whether the item is being deleted.
For this reason, I made a POJO that we can use to contain an integer (that we use as both an itemId and a visual representation and a boolean indicating that this row is being deleted or not.
When you decide to delete entries (in this example adapter.notifyItemRangeRemoved(3, 8);), you must also set the associated Pojo to being deleted (in this example pojo.beingDeleted = true;).
The position of the divider when beingDeleted, is reset to the colour of the parent view. In order to cover up the divider.
I am not very fond of using the dataset itself to manage the state of its parent list. There is perhaps a better way.
The result visualized
The Activity:
public class MainActivity extends AppCompatActivity {
private static final int VERTICAL_ITEM_SPACE = 8;
private List<Pojo> mDataset = new ArrayList<Pojo>();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
for(int i = 0; i < 30; i++) {
mDataset.add(new Pojo(i));
}
final RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recyclerView);
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
recyclerView.setLayoutManager(layoutManager);
recyclerView.addItemDecoration(new VerticalSpaceItemDecoration(VERTICAL_ITEM_SPACE));
recyclerView.addItemDecoration(new DividerItemDecoration(this));
RecyclerView.ItemAnimator ia = recyclerView.getItemAnimator();
ia.setRemoveDuration(4000);
final Adapter adapter = new Adapter(mDataset);
recyclerView.setAdapter(adapter);
(new Handler(Looper.getMainLooper())).postDelayed(new Runnable() {
#Override
public void run() {
int index = 0;
Iterator<Pojo> it = mDataset.iterator();
while(it.hasNext()) {
Pojo pojo = it.next();
if(index >= 3 && index <= 10) {
pojo.beingDeleted = true;
it.remove();
}
index++;
}
adapter.notifyItemRangeRemoved(3, 8);
}
}, 2000);
}
public class Adapter extends RecyclerView.Adapter<Holder> {
private List<Pojo> mDataset;
public Adapter(#NonNull final List<Pojo> dataset) {
setHasStableIds(true);
mDataset = dataset;
}
#Override
public Holder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.adapter_cell, parent, false);
return new Holder(view);
}
#Override
public void onBindViewHolder(final Holder holder, final int position) {
final Pojo data = mDataset.get(position);
holder.itemView.setTag(data);
holder.textView.setText("Test "+data.dataItem);
}
#Override
public long getItemId(int position) {
return mDataset.get(position).dataItem;
}
#Override
public int getItemCount() {
return mDataset.size();
}
}
public class Holder extends RecyclerView.ViewHolder {
public TextView textView;
public Holder(View itemView) {
super(itemView);
textView = (TextView) itemView.findViewById(R.id.text);
}
}
public class Pojo {
public int dataItem;
public boolean beingDeleted = false;
public Pojo(int dataItem) {
this.dataItem = dataItem;
}
}
public class DividerItemDecoration extends RecyclerView.ItemDecoration {
private final int[] ATTRS = new int[]{android.R.attr.listDivider};
private Paint mOverwritePaint;
private Drawable mDivider;
/**
* Default divider will be used
*/
public DividerItemDecoration(Context context) {
final TypedArray styledAttributes = context.obtainStyledAttributes(ATTRS);
mDivider = styledAttributes.getDrawable(0);
styledAttributes.recycle();
initializePaint();
}
/**
* Custom divider will be used
*/
public DividerItemDecoration(Context context, int resId) {
mDivider = ContextCompat.getDrawable(context, resId);
initializePaint();
}
private void initializePaint() {
mOverwritePaint = new Paint();
mOverwritePaint.setColor(ContextCompat.getColor(MainActivity.this, android.R.color.background_light));
}
#Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
int left = parent.getPaddingLeft();
int right = parent.getWidth() - parent.getPaddingRight();
int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
View child = parent.getChildAt(i);
RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
int top = child.getBottom() + params.bottomMargin;
int bottom = top + mDivider.getIntrinsicHeight();
Pojo item = (Pojo) child.getTag();
if(item.beingDeleted) {
c.drawRect(left, top, right, bottom, mOverwritePaint);
} else {
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(c);
}
}
}
}
public class VerticalSpaceItemDecoration extends RecyclerView.ItemDecoration {
private final int mVerticalSpaceHeight;
public VerticalSpaceItemDecoration(int mVerticalSpaceHeight) {
this.mVerticalSpaceHeight = mVerticalSpaceHeight;
}
#Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
RecyclerView.State state) {
outRect.bottom = mVerticalSpaceHeight;
}
}
}
The Activity Layout
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingLeft="#dimen/activity_horizontal_margin"
android:paddingRight="#dimen/activity_horizontal_margin"
android:paddingTop="#dimen/activity_vertical_margin"
android:paddingBottom="#dimen/activity_vertical_margin"
android:background="#android:color/background_light"
tools:context="test.dae.myapplication.MainActivity">
<android.support.v7.widget.RecyclerView
android:id="#+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</RelativeLayout>
The RecyclerView "row" Layout
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/text"
android:padding="8dp">
</TextView>
I think the ItemDecorator you use to draw a divider after every row is messing things up when swipe to delete is performed.
Instead of Using ItemDecorator to draw a Divider in a recyclerview, add a view at the end of your RecyclerView child layout design.which will draw a divider line like ItemDecorator.
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
>
<!-- child layout Design !-->
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#android:color/darker_gray"
android:layout_gravity="bottom"
/>
</Linearlayout>

RecyclerView number of visible items

I am creating a horisontal RecyclerView in my app.
It has to show 2 images on the screen at a time (so width of each image has to be 50% of the screen).
For now it works fine but each item consums all width of the screen.
Here is my code
mRecyclerView = (RecyclerView) view.findViewById(R.id.recycler_main_ads);
LinearLayoutManager mLinearLayoutManager = new LinearLayoutManager(getActivity());
mLinearLayoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
mRecyclerView.setLayoutManager(mLinearLayoutManager);
RecyclerViewAdapter adapter = new RecyclerViewAdapter(tmp, R.layout.lv_main_screen);
mRecyclerView.setAdapter(adapter);
Here is layout of an item
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:weightSum="1">
<ImageView
android:id="#+id/iv_main_ad"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="0.5"
android:scaleType="fitXY"
android:src="#drawable/baner_gasoline"
/>
</LinearLayout>
As you can see I tried to use Layout_gravity="0.5",
But it doesn't help.
I tried to specify layout_width = ...dp but I can not get exactly half of the screen.
I am thinking of adding another ImageView into item layout, but in this case I will have troubles with the adapter, because I want to implemnt circled (infinity) horizontal listview
here is my adapter:
public class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerViewAdapter.MyHolder> {
private List<Integer> mImages;
private int itemLayout;
public RecyclerViewAdapter(ArrayList<Integer> imageResourceIds, int itemLayout) {
this.mImages = imageResourceIds;
this.itemLayout = itemLayout;
}
#Override
public MyHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(itemLayout, parent, false);
return new MyHolder(v);
}
#Override
public void onBindViewHolder(MyHolder holder, int position) {
holder.adIv.setImageResource(mImages.get(position));
}
#Override
public int getItemCount() {
return mImages.size();
}
public static class MyHolder extends RecyclerView.ViewHolder {
protected ImageView adIv;
private MyHolder(View v) {
super(v);
this.adIv = (ImageView) v.findViewById(R.id.iv_main_ad);
}
}
}
For You need to calculate the width of the screen and set the width dynamically below is the my code
Add below code in your ViewHolder initilisation
llImg = (LinearLayout) itemView.findViewById(R.id.llImg);
llImg.getLayoutParams().width = (int) (Utils.getScreenWidth(itemView.getContext()) / 2);
llImg.getLayoutParams().height = (int) (Utils.getScreenWidth(itemView.getContext()) / 2);
imgView = (ImageView) itemView.findViewById(R.id.imgView);
The Layout file is here
<LinearLayout
android:id="#+id/llImg"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center">
<ImageView
android:id="#+id/imgView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
</LinearLayout>
Make one Utils.java
public static int getScreenWidth(Context context) {
if (screenWidth == 0) {
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
Display display = wm.getDefaultDisplay();
Point size = new Point();
display.getSize(size);
screenWidth = size.x;
}
return screenWidth;
}
Hope this will help you !

GridLayoutManager - how to auto fit columns?

I have a RecyclerView with a GridLayoutManager that displays Card Views. I want the cards to rearrange according to the screen size (the Google Play app does this kind of thing with its app cards). Here is an example:
Here is how my app looks at the moment:
As you can see the cards just stretch and don't fit the empty space that is made from the orientation change. So how can I do this?
Code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Json;
using System.Threading;
using System.Threading.Tasks;
using Android.Media;
using Android.App;
using Android.Support.V4.App;
using Android.Support.V4.Content.Res;
using Android.Support.V4.Widget;
using Android.Support.V7.Widget;
using Android.Content;
using Android.OS;
using Android.Runtime;
using Android.Util;
using Android.Views;
using Android.Widget;
using Android.Net;
using Android.Views.Animations;
using Android.Graphics;
using Android.Graphics.Drawables;
using Newtonsoft.Json;
using *******.Adapters;
using *******.Models;
namespace *******.Fragments {
public class Dashboard : GridLayoutBase {
private ISharedPreferences pref;
private SessionManager session;
private string cookie;
private DeviceModel deviceModel;
private RecyclerView recyclerView;
private RecyclerView.Adapter adapter;
// private RecyclerView.LayoutManager layoutManager;
private GridLayoutManager gridLayoutManager;
private List<ItemData> itemData;
private Bitmap lastPhotoBitmap;
private Drawable lastPhotoDrawable;
private static Activity activity;
private ProgressDialog progressDialog;
private TextView noData;
private const string URL_DASHBOARD = "http://192.168.1.101/appapi/getdashboard";
private const string URL_DATA = "http://192.168.1.101/appapi/getdata";
public override void OnCreate(Bundle bundle) {
base.OnCreate(bundle);
activity = Activity;
session = new SessionManager();
pref = activity.GetSharedPreferences("UserSession", FileCreationMode.Private);
cookie = pref.GetString("PHPSESSID", string.Empty);
}
public async override void OnStart() {
base.OnStart();
progressDialog = ProgressDialog.Show(activity, String.Empty, GetString(Resource.String.loading_text));
progressDialog.Window.ClearFlags(WindowManagerFlags.DimBehind);
await GetDevicesInfo();
if (deviceModel.Error == "true" && deviceModel.ErrorType == "noSensors") {
recyclerView.Visibility = ViewStates.Gone;
noData.Visibility = ViewStates.Visible;
progressDialog.Hide();
return;
} else {
recyclerView.Visibility = ViewStates.Visible;
noData.Visibility = ViewStates.Gone;
await PopulateSensorStates();
}
// DisplayLastPhoto();
adapter = new ViewAdapter(itemData);
new System.Threading.Thread(new System.Threading.ThreadStart(() => {
activity.RunOnUiThread(() => {
recyclerView.SetAdapter(adapter);
});
})).Start();
progressDialog.Hide();
}
public async Task GetDevicesInfo() {
var jsonFetcher = new JsonFetcher();
JsonValue jsonDashboard = await jsonFetcher.FetchDataWithCookieAsync(URL_DASHBOARD, cookie);
deviceModel = new DeviceModel();
deviceModel = JsonConvert.DeserializeObject<DeviceModel>(jsonDashboard);
}
// Shows sensor states
public async Task PopulateSensorStates() {
itemData = new List<ItemData>();
string lastValue = String.Empty;
foreach (var sensor in this.deviceModel.Sensors) {
var sensorImage = ResourcesCompat.GetDrawable(Resources, Resource.Drawable.smoke_red, null);
switch (sensor.Type) {
case "2":
var jsonFetcher = new JsonFetcher();
JsonValue jsonData = await jsonFetcher.FetchSensorDataAsync(URL_DATA, sensor.Id, "DESC", "1", cookie);
var deviceModel = new DeviceModel();
deviceModel = JsonConvert.DeserializeObject<DeviceModel>(jsonData);
lastValue = deviceModel.SensorData.Last().Value;
break;
case "4":
await RenderLastCameraPhoto();
sensorImage = new BitmapDrawable(Resources, lastPhotoBitmap);
break;
}
itemData.Add(new ItemData() {
id = sensor.Id,
value = lastValue,
type = sensor.Type,
image = sensorImage,
title = sensor.Name.First().ToString().ToUpper() + sensor.Name.Substring(1).ToLower(),
});
}
}
// Shows the last camera photo
public async Task RenderLastCameraPhoto() {
if (deviceModel.Error == "true" && deviceModel.ErrorType == "noPhoto") {
//TODO: Show a "No photo" picture
} else {
string url = deviceModel.LastPhotoLink;
lastPhotoBitmap = await new ImageDownloader().GetImageBitmapFromUrlAsync(url, activity, 300, 300);
}
}
public async void UpdateData(bool isSwipeRefresh) {
await GetDevicesInfo();
if (deviceModel.Error == "true" && deviceModel.ErrorType == "noSensors") {
recyclerView.Visibility = ViewStates.Gone;
noData.Visibility = ViewStates.Visible;
return;
} else {
recyclerView.Visibility = ViewStates.Visible;
noData.Visibility = ViewStates.Gone;
await PopulateSensorStates();
}
adapter = new ViewAdapter(itemData);
new System.Threading.Thread(new System.Threading.ThreadStart(() => {
activity.RunOnUiThread(() => {
recyclerView.SetAdapter(adapter);
});
})).Start();
adapter.NotifyDataSetChanged();
}
public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.Inflate(Resource.Layout.Dashboard, container, false);
noData = view.FindViewById<TextView>(Resource.Id.no_data_title);
SwipeRefreshLayout swipeRefreshLayout = view.FindViewById<SwipeRefreshLayout>(Resource.Id.swipe_container);
// swipeRefreshLayout.SetColorSchemeResources(Color.LightBlue, Color.LightGreen, Color.Orange, Color.Red);
// On refresh button press/swipe, updates the recycler view with new data
swipeRefreshLayout.Refresh += (sender, e) => {
UpdateData(true);
swipeRefreshLayout.Refreshing = false;
};
var gridLayoutManager = new GridLayoutManager(activity, 2);
recyclerView = view.FindViewById<RecyclerView>(Resource.Id.dashboard_recycler_view);
recyclerView.HasFixedSize = true;
recyclerView.SetLayoutManager(gridLayoutManager);
recyclerView.SetItemAnimator(new DefaultItemAnimator());
recyclerView.AddItemDecoration(new SpaceItemDecoration(15));
return view;
}
public class ViewAdapter : RecyclerView.Adapter {
private List<ItemData> itemData;
public string sensorId;
public string sensorType;
private ImageView imageId;
private TextView sensorValue;
private TextView sensorTitle;
public ViewAdapter(List<ItemData> itemData) {
this.itemData = itemData;
}
public class ItemView : RecyclerView.ViewHolder {
public View mainView { get; set; }
public string id { get; set; }
public string type { get; set; }
public ImageView image { get; set; }
// public TextView value { get; set; }
public TextView title { get; set; }
public ItemView(View view) : base(view) {
mainView = view;
}
}
public override RecyclerView.ViewHolder OnCreateViewHolder(ViewGroup parent, int viewType) {
View itemLayoutView = LayoutInflater.From(parent.Context).Inflate(Resource.Layout.DashboardItems, null);
CardView cardView = itemLayoutView.FindViewById<CardView>(Resource.Id.dashboard_card_view);
imageId = itemLayoutView.FindViewById<ImageView>(Resource.Id.sensor_image);
// sensorValue = itemLayoutView.FindViewById<TextView>(Resource.Id.sensor_value);
sensorTitle = itemLayoutView.FindViewById<TextView>(Resource.Id.sensor_title);
var viewHolder = new ItemView(itemLayoutView) {
id = sensorId,
type = sensorType,
image = imageId,
// value = sensorValue,
title = sensorTitle
};
return viewHolder;
}
public override void OnBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) {
ItemView itemHolder = viewHolder as ItemView;
itemHolder.image.SetImageDrawable(itemData[position].image);
if (itemData[position].type == "2") { // Temperature
itemHolder.title.Text = itemData[position].title + ": " + itemData[position].value;
} else {
itemHolder.title.Text = itemData[position].title;
}
var bundle = new Bundle();
var dualColumnList = new DualColumnList();
var gallery = new Gallery();
EventHandler clickUpdateViewEvent = ((sender, e) => {
bundle.PutString("id", itemData[position].id);
gallery.Arguments = bundle;
dualColumnList.Arguments = bundle;
if (itemData[position].type == "4") { // Camera
((FragmentActivity)activity).ShowFragment(gallery, itemData[position].title, itemData[position].type, true);
} else {
((FragmentActivity)activity).ShowFragment(dualColumnList, itemData[position].title, itemData[position].type, true);
}
});
itemHolder.image.Click += clickUpdateViewEvent;
// itemHolder.value.Click += clickUpdateViewEvent;
itemHolder.title.Click += clickUpdateViewEvent;
}
public override int ItemCount {
get { return itemData.Count; }
}
}
public class ItemData {
public string id { get; set; }
public string type { get; set; }
public Drawable image { get; set; }
public string value { get; set; }
public string title { get; set; }
}
}
}
Fragment Layout:
<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.SwipeRefreshLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/swipe_container"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_horizontal"
android:weightSum="1">
<RelativeLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="0.9"
android:scrollbars="vertical">
<android.support.v7.widget.RecyclerView
android:id="#+id/dashboard_recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<TextView
android:text="#string/no_data_text"
android:id="#+id/no_data_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="30sp"
android:gravity="center"
android:layout_centerInParent="true" />
</RelativeLayout>
</LinearLayout>
</android.support.v4.widget.SwipeRefreshLayout>
Fragment Items Layout:
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/dashboard_card_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:orientation="vertical"
android:foreground="?android:attr/selectableItemBackground">
<ImageView
android:id="#+id/sensor_image"
android:layout_width="120dp"
android:layout_height="120dp"
android:paddingTop="5dp"
android:layout_alignParentTop="true" />
<!-- <TextView
android:id="#+id/sensor_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="30sp"
android:layout_below="#id/sensor_image"
android:gravity="center" />-->
<TextView
android:id="#+id/sensor_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="23sp"
android:layout_below="#id/sensor_image"
android:gravity="center"
android:layout_alignParentBottom="true" />
</LinearLayout>
</android.support.v7.widget.CardView>
You can calculate available number of columns, given a desired column width, and load the image as calculated. Define a static funtion to calculate as:
public class Utility {
public static int calculateNoOfColumns(Context context, float columnWidthDp) { // For example columnWidthdp=180
DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
float screenWidthDp = displayMetrics.widthPixels / displayMetrics.density;
int noOfColumns = (int) (screenWidthDp / columnWidthDp + 0.5); // +0.5 for correct rounding to int.
return noOfColumns;
}
}
And then when using it in the activity or fragment you can do like this :
int mNoOfColumns = Utility.calculateNoOfColumns(getApplicationContext());
............
mGridLayoutManager = new GridLayoutManager(this, mNoOfColumns);
The GridLayoutManager's constructor has an argument spanCount that is
The number of columns in the grid
You can initialize the manager with an integer resource value and provide different values for different screens (i.e. values-w600, values-large, values-land).
I tried #Riten answer and worked funtastic!! But I wasn't happy with the hardcoded "180"
So I modified to this:
public class ColumnQty {
private int width, height, remaining;
private DisplayMetrics displayMetrics;
public ColumnQty(Context context, int viewId) {
View view = View.inflate(context, viewId, null);
view.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
width = view.getMeasuredWidth();
height = view.getMeasuredHeight();
displayMetrics = context.getResources().getDisplayMetrics();
}
public int calculateNoOfColumns() {
int numberOfColumns = displayMetrics.widthPixels / width;
remaining = displayMetrics.widthPixels - (numberOfColumns * width);
// System.out.println("\nRemaining\t" + remaining + "\nNumber Of Columns\t" + numberOfColumns);
if (remaining / (2 * numberOfColumns) < 15) {
numberOfColumns--;
remaining = displayMetrics.widthPixels - (numberOfColumns * width);
}
return numberOfColumns;
}
public int calculateSpacing() {
int numberOfColumns = calculateNoOfColumns();
// System.out.println("\nNumber Of Columns\t"+ numberOfColumns+"\nRemaining Space\t"+remaining+"\nSpacing\t"+remaining/(2*numberOfColumns)+"\nWidth\t"+width+"\nHeight\t"+height+"\nDisplay DPI\t"+displayMetrics.densityDpi+"\nDisplay Metrics Width\t"+displayMetrics.widthPixels);
return remaining / (2 * numberOfColumns);
}
}
Where "viewId" is the layout to be used as views in the RecyclerView like in R.layout.item_for_recycler
Not sure though about the impact of View.inflate as I only use it to get the Width, nothing else.
Then on the GridLayoutManager I do:
GridLayoutManager gridLayoutManager = new GridLayoutManager(this, Utility.columnQty(this, R.layout.item_for_recycler));
UPDATE:
I added more lines to the code as I use it to get a minimum width spacing in the Grid.
Calculate spacing:
recyclerPatternsView.addItemDecoration(new GridSpacing(columnQty.calculateSpacing()));
GridSpacing:
public class GridSpacing extends RecyclerView.ItemDecoration {
private final int spacing;
public GridSpacing(int spacing) {
this.spacing = spacing;
}
#Override
public void getItemOffsets(#NonNull Rect outRect, #NonNull View view, #NonNull RecyclerView parent, #NonNull RecyclerView.State state) {
outRect.left = spacing;
outRect.right = spacing;
outRect.bottom = spacing;
outRect.top = spacing;
}
}
A simple Kotlin extension function. Pass the width in DP (same as the overall width in the item xml)
/**
* #param columnWidth - in dp
*/
fun RecyclerView.autoFitColumns(columnWidth: Int) {
val displayMetrics = this.context.resources.displayMetrics
val noOfColumns = ((displayMetrics.widthPixels / displayMetrics.density) / columnWidth).toInt()
this.layoutManager = GridLayoutManager(this.context, noOfColumns)
}
Call it like every other extension...
myRecyclerView.autoFitColumns(200)
public class AutoFitGridLayoutManager extends GridLayoutManager {
private int columnWidth;
private boolean columnWidthChanged = true;
public AutoFitGridLayoutManager(Context context, int columnWidth) {
super(context, 1);
setColumnWidth(columnWidth);
}
public void setColumnWidth(int newColumnWidth) {
if (newColumnWidth > 0 && newColumnWidth != columnWidth) {
columnWidth = newColumnWidth;
columnWidthChanged = true;
}
}
#Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
if (columnWidthChanged && columnWidth > 0) {
int totalSpace;
if (getOrientation() == VERTICAL) {
totalSpace = getWidth() - getPaddingRight() - getPaddingLeft();
} else {
totalSpace = getHeight() - getPaddingTop() - getPaddingBottom();
}
int spanCount = Math.max(1, totalSpace / columnWidth);
setSpanCount(spanCount);
columnWidthChanged = false;
}
super.onLayoutChildren(recycler, state);
}
}
now you can set LayoutManager to recycle view
here i have set 250px
AutoFitGridLayoutManager layoutManager = new AutoFitGridLayoutManager(this, 250);
recycleView.setLayoutManager(layoutManager)
show the belove image
Constructor new GridLayoutManager(activity, 2) is about GridLayoutManager(Context context, int spanCount) where spanCount is the number of columns in the grid.
Best way is to check window/view width and base on this width count how many spans you want to show.
Use this function, and put margins for cell layout in XML instead using decoration.
public int getNumberOfColumns() {
View view = View.inflate(this, R.layout.row_layout, null);
view.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
int width = view.getMeasuredWidth();
int count = getResources().getDisplayMetrics().widthPixels / width;
int remaining = getResources().getDisplayMetrics().widthPixels - width * count;
if (remaining > width - 15)
count++;
return count;
}
Set in recyclerView initialization:
recyclerView.setLayoutManager(new GridLayoutManager(this, 4));

Android: first element of a GridView is misplaced in galaxy S3 and S4

I'm building an app that has a mini slotMachine game inside. The problem is that I use a GridView for each column of the slot and for some devices (not all) the first symbol of the slot has a space above that comes from nowhere...
The table of the slot is 3 rows X 5 columns.
The space doesn't come from the calculation of the symbol width and height because I've found that with two devices withe perfectly identical resolution and density (Galaxy tab 10.1 and Galaxy note 10.1) the spacing is different: 0 for one and 5 for the other.
Any help?
My MainActivity:
public class MainActivity extends Activity{
SlotView slot;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
slot=new SlotView(this);
slot.refreshSlotView("AAAAAAAAAAAAAAA");
}
}
SlotView:
public class SlotView {
private ArrayList<GridAdapter> adapter=new ArrayList<GridAdapter>();
public float H_RATIO=2/3; //height ratio of the grid based on the device
public float W_RATIO=5/6; //width ratio of the grid based on the device
Activity activity;
String path = "file:///android_asset/Slot/Slot-";
public SlotView(Context c){
activity = (Activity) c;
DisplayMetrics dm = new DisplayMetrics();
activity.getWindowManager().getDefaultDisplay().getMetrics(dm);
final int hDisplay=dm.heightPixels;
int wImage=(1024*hDisplay)/768; //width of the central image
float heightGrid=(hDisplay*2)/3; //grid height
float widthGrid=((wImage*5)/6); //grid width
float heightSymbol=(heightGrid-(8))/3; // symbol height (3 symbols for column with a spacing of 4 px)
//relative layout for the container table
RelativeLayout.LayoutParams params=new RelativeLayout.LayoutParams((int)widthGrid, (int)heightGrid);
params.addRule(RelativeLayout.CENTER_IN_PARENT);
// grid of the slot
LinearLayout grid_layout=(LinearLayout)activity.findViewById(R.id.layout_grid);
grid_layout.setLayoutParams(params);
grid_layout.setGravity(Gravity.TOP);
// gridview of the columns
LinearLayout.LayoutParams params_grid=new LinearLayout.LayoutParams((int)heightSymbol, (int)heightGrid);
for(int i=0; i<5; i++){ // five columns
GridView slot=new GridView(activity);
slot.setScrollContainer(false);
slot.setVerticalScrollBarEnabled(false);
slot.setHorizontalScrollBarEnabled(false);
if(i<4) {
params_grid.setMargins(0,0,4,0); // spacing between each column
}
slot.setLayoutParams(params_grid);
slot.setVerticalSpacing(4);
slot.setPadding(0, 0, 0, 0);
slot.setColumnWidth((int)heightSymbol);
slot.setNumColumns(GridView.AUTO_FIT);
slot.setGravity(Gravity.CENTER);
GridAdapter grid_adapter=new GridAdapter(activity, (int)heightSymbol, (int)heightSymbol);
adapter.add(grid_adapter);
slot.setAdapter(grid_adapter);
grid_layout.addView(slot);
}
}
public void refreshSlotView(String configTris){
for(int pos=0; pos<5; pos++){
String[] mThumbIds=new String[3];
int z=0;
for(int i=pos; z<3 && i<configTris.length(); i=i+5){
char letter=configTris.charAt(i);
mThumbIds[z]=path + letter + ".png";
z++;
}
adapter.get(pos).setArrayOfImage(mThumbIds);
adapter.get(pos).notifyDataSetChanged();
}
}
}
The adapter:
public class GridAdapter extends BaseAdapter {
private Context mContext;
private String[] mThumbIds={};
private int w;
private int h;
public GridAdapter(Context c, int w, int h) {
mContext = c;
this.w=w;
this.h=h;
}
public int getCount() {
return mThumbIds.length;
}
public Object getItem(int position) {
return null;
}
public long getItemId(int position) {
return 0;
}
public View getView(int position, View convertView, ViewGroup parent) {
View view=convertView;
ImageView imageView;
if (convertView == null) {
LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
view = inflater.inflate(R.layout.slot_element, null);
RelativeLayout rel=(RelativeLayout)view.findViewById(R.id.rel);
rel.setLayoutParams(new GridView.LayoutParams(w, h));
imageView=(ImageView)view.findViewById(R.id.image);
RelativeLayout.LayoutParams params=new RelativeLayout.LayoutParams(w, h);
params.addRule(RelativeLayout.CENTER_IN_PARENT);
imageView.setLayoutParams(params);
imageView.setScaleType(ImageView.ScaleType.FIT_XY);
}else{
imageView = (ImageView) view.findViewById(R.id.image);
}
Picasso.with(mContext).load(mThumbIds[position]).into(imageView); // puts the image in the imageview
return view;
}
public void setArrayOfImage(String[] images){
this.mThumbIds=images;
}
public void showSymbolAtIndex(char letter, int position){
mThumbIds[position]="file:///android_asset/" + "Slot/Slot-" + letter + ".png";
notifyDataSetChanged();
}
}
This is the MainActivity Layout:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:clickable="true"
android:orientation="vertical" >
<LinearLayout
android:id="#+id/layout_grid"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:orientation="horizontal" >
</LinearLayout>
</RelativeLayout>
And the slot_element Layout:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/rel"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<ImageView
android:id="#+id/image"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="fitXY" />
</RelativeLayout>
Now that's the result:
And that's the proof of the existing spacing (if I drag one of the columns up):

Categories

Resources