I'm setting different height for my custom listview items based on screen orientation, and as in code below I listen to screen orientation changes, and set a global value according to it, and when getView(...) gets called on listview items I set the height of the converted view.
My question is, is there a better solution than this?
How bad this approach affect the UI loading speed process?
I'm targeting API14+
P.S: (200 & 300) below are added here as example, they are not fixed at runtime, they are changed during runtime according to screen dpi.
int mConvertViewHeight;
#Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE)
{
mConvertViewHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 200, getResources().getDisplayMetrics());
}
else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT)
{
mConvertViewHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 300, getResources().getDisplayMetrics());
}
}
In my listview custom array adapter
#Override
public View getView(final int aConvertViewPosition, View aConvertView, ViewGroup parent)
{
mParams = aConvertView.getLayoutParams();
mParams.height = mMainActivity.mConvertViewHeight;
}
If you need just different item view in portrait/landscape mode, you can create different layouts in layout-land and layout-port folders
Inside my fragment I'm setting my GridLayout in the following way:
mRecycler.setLayoutManager(new GridLayoutManager(rootView.getContext(), 2));
So, I just want to change that 2 for a 4 when the user rotates the phone/tablet. I've read about onConfigurationChanged and I tried to make it work for my case, but it isn't going in the right way. When I rotate my phone, the app crashes...
Could you tell me how to solve this issue?
Here is my approach to find the solution, which is not working correctly:
#Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
int orientation = newConfig.orientation;
if (orientation == Configuration.ORIENTATION_PORTRAIT) {
mRecycler.setLayoutManager(new GridLayoutManager(mContext, 2));
} else if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
mRecycler.setLayoutManager(new GridLayoutManager(mContext, 4));
}
}
If you have more than one condition or use the value in multiple places this can go out of hand pretty fast. I suggest to create the following structure:
res
- values
- integers.xml
- values-land
- integers.xml
with res/values/integers.xml being:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<integer name="gallery_columns">2</integer>
</resources>
and res/values-land/integers.xml being:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<integer name="gallery_columns">4</integer>
</resources>
And the code then becomes (and forever stays) like this:
final int columns = getResources().getInteger(R.integer.gallery_columns);
mRecycler.setLayoutManager(new GridLayoutManager(mContext, columns));
You can easily see how easy it is to add new ways of determining the column count, for example using -w500dp/-w600dp/-w700dp resource folders instead of -land.
It's also quite easy to group these folders into separate resource folder in case you don't want to clutter your other (more relevant) resources:
android {
sourceSets.main.res.srcDir 'src/main/res-overrides' // add alongside src/main/res
}
Try handling this inside your onCreateView method instead since it will be called each time there's an orientation change:
if(getActivity().getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT){
mRecycler.setLayoutManager(new GridLayoutManager(mContext, 2));
}
else{
mRecycler.setLayoutManager(new GridLayoutManager(mContext, 4));
}
In addition to the answers. It can be also done using XML attributes only. Below is the code.
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/pax_seat_map_rv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:spanCount="3"
android:orientation="vertical"
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager" />
The Recycle View supports AutofitRecycleView.
You need to add android:numColumns="auto_fit" in your xml file.
You can refer to this AutofitRecycleViewLink
A more robust way to determine the no. of columns would be to calculate it based on the screen width and at runtime. I normally use the following function for that.
public static int calculateNoOfColumns(Context context) {
DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
float dpWidth = displayMetrics.widthPixels / displayMetrics.density;
int scalingFactor = 200; // You can vary the value held by the scalingFactor
// variable. The smaller it is the more no. of columns you can display, and the
// larger the value the less no. of columns will be calculated. It is the scaling
// factor to tweak to your needs.
int columnCount = (int) (dpWidth / scalingFactor);
return (columnCount>=2?columnCount:2); // if column no. is less than 2, we still display 2 columns
}
It is a more dynamic method to accurately calculate the no. of columns. This will be more adaptive for users of varying screen sizes without being resticted to only two possible values.
NB: You can vary the value held by the scalingFactor variable. The smaller it is the more no. of columns you can display, and the larger the value the less no. of columns will be calculated. It is the scaling factor to tweak to your needs.
In the onCreate () event you can use StaggeredGridLayoutManager
mRecyclerView = (RecyclerView) v.findViewById(R.id.recyclerView);
mStaggeredGridLayoutManager = new StaggeredGridLayoutManager(
1, //number of grid columns
GridLayoutManager.VERTICAL);
mRecyclerView.setLayoutManager(mStaggeredGridLayoutManager);
Then when the user rotates the screen capture the event, and change the number of columns automatically
#Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
if (getActivity().getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {
mStaggeredGridLayoutManager.setSpanCount(1);
} else {
//show in two columns
mStaggeredGridLayoutManager.setSpanCount(2);
}
}
I ended up handling this in the onCreate method.
private RecyclerView recyclerView = null;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
recyclerView.setHasFixedSize(true);
Configuration orientation = new Configuration();
if(this.recyclerView.getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {
recyclerView.setLayoutManager(new GridLayoutManager(this, 2));
} else if (this.recyclerView.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
recyclerView.setLayoutManager(new GridLayoutManager(this, 4));
}
connectGetApiData();
}
It worked out perfectly for my app.
You can implement the method in your recyclerView onMeasure.
First, create the java class AutofitRecyclerView
public class AutofitRecyclerView extends RecyclerView {
//private GridLayoutManager manager;
private StaggeredGridLayoutManager manager;
private int columnWidth = -1;
public AutofitRecyclerView(Context context) {
super(context);
init(context, null);
}
public AutofitRecyclerView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
public AutofitRecyclerView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context, attrs);
}
private void init(Context context, AttributeSet attrs) {
if (attrs != null) {
int[] attrsArray = {
android.R.attr.columnWidth
};
TypedArray array = context.obtainStyledAttributes(attrs, attrsArray);
columnWidth = array.getDimensionPixelSize(0, -1);
array.recycle();
}
manager = new StaggeredGridLayoutManager(1, GridLayoutManager.VERTICAL);
setLayoutManager(manager);
}
#Override
protected void onMeasure(int widthSpec, int heightSpec) {
super.onMeasure(widthSpec, heightSpec);
if (columnWidth > 0) {
int spanCount = Math.max(1, getMeasuredWidth() / columnWidth);
manager.setSpanCount(spanCount);
}
}}
In your xlm layout file activity_main.xml
<yourpackagename.AutofitRecyclerView
android:id="#+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:columnWidth="#dimen/column_width"
android:clipToPadding="false"/>
Then set the variable to the width of each item in the file size of the values folder values/dimens.xml
<resources>
<dimen name="column_width">250dp</dimen>
</resources>
It can be for different screen resolutions values-xxhdpi/dimens.xml
<resources>
<dimen name="column_width">280dp</dimen>
</resources>
In your activity in the onCreate event place the following code
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
recyclerView.addItemDecoration(new MarginDecoration(this));
recyclerView.setHasFixedSize(true);
recyclerView.setAdapter(new NumberedAdapter(50));
}
I will try to explain it step by step. And you can check integers.xml, integers.xml (land) and MainFragment files of github project that I shared. You will see that number of columns will change based on orientation or screen size(tablet vs phone)(I will explain only how to do it when orientation changed, since the question is only about it).
https://github.com/b-basoglu/NewsApp/blob/master/app/src/main/java/com/news/newsapp/ui/main/MainFragment.kt
Create a resources file called integers.xml. -> Open the res folder in the Project > Android pane, right-click on the values folder, and select New > Values resource file.
Name the file integers.xml and click OK.
Create an integer constant between the tags called grid_column_count and set it equal to 2:
< integer name="grid_column_count">2</ integer >
Create another values resource file, again called integers.xml; however, the name will be modified as you add resource qualifiers from the Available qualifiers pane. The resource qualifiers are used to label resource configurations for various situations.
Select Orientation in the Available qualifiers pane, and press the >> symbol in the middle of the dialog to assign this qualifier.
Change the Screen orientation menu to Landscape, and notice how the directory name values-land appears. This is the essence of resource qualifiers: the directory name tells Android when to use that specific layout file. In this case, that is when the phone is rotated to landscape mode.
Click OK to generate the new layout file.
Copy the integer constant you created into this new resource file, but change the value to 4.
< integer name="grid_column_count">4</ integer >
[You have these files][1]
Now all you have to do is re-assigning your span count when configuration changed as below:
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
gridLayoutManager?.let {
it.spanCount = resources.getInteger(R.integer.grid_column_count)
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
gridLayoutManager = GridLayoutManager(requireContext(),
resources.getInteger(R.integer.grid_column_count))
${yourRecyclerView}.layoutManager = gridLayoutManager
...
[1]: https://i.stack.imgur.com/3NZdq.png
I have an activity whose layout I need to change after a rotation and part of the layout is a graph that is drawn using the width and height of the view that it will be placed into. The first time my code runs, the graph is drawn correctly, however after the rotation the width and height of the container view are not correct, in fact they appear to be the view as if it was not rotated.
Here is what I have so far,
In my manifest for the activity I am working:
android:configChanges="keyboardHidden|orientation|screenSize"
In my activity I have these following methods:
onCreate
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Bundle extras = getIntent().getExtras();
patient_id = extras.getInt("patient_id");
patient_name = extras.getString("patient_name");
historyDurationType = 12;
constructLayout();
}
constructLayout
public void constructLayout(){
if(landScape){
setContentView(R.layout.activity_bg_history_static_land);
//Set buttons
btnTwelve = (Button)findViewById(R.id.btnTwelveHoursLand);
btnTwentyFour = (Button)findViewById(R.id.btnTwentyFourHoursLand);
btnSeven= (Button)findViewById(R.id.btnSevenDaysLand);
btnTwelve.setOnClickListener(this);
btnTwentyFour.setOnClickListener(this);
btnSeven.setOnClickListener(this);
btnTwelve.setBackgroundColor(getResources().getColor(R.color.light_blue_regular));
btnTwentyFour.setBackgroundResource(android.R.drawable.btn_default);
btnSeven.setBackgroundResource(android.R.drawable.btn_default);
}else{
setContentView(R.layout.activity_bg_history_static);
//Set buttons
btnTwelve = (Button)findViewById(R.id.btnTwelveHours);
btnTwentyFour = (Button)findViewById(R.id.btnTwentyFourHours);
btnSeven= (Button)findViewById(R.id.btnSevenDays);
btnTwelve.setOnClickListener(this);
btnTwentyFour.setOnClickListener(this);
btnSeven.setOnClickListener(this);
btnTwelve.setBackgroundColor(getResources().getColor(R.color.light_blue_regular));
btnTwentyFour.setBackgroundResource(android.R.drawable.btn_default);
btnSeven.setBackgroundResource(android.R.drawable.btn_default);
btnComment = (Button)findViewById(R.id.btnCommentGraph);
btnComment.setOnClickListener(this);
populateOtherContent(officialReadings12);
TextView tvStats = (TextView)findViewById(R.id.txtStatistics);
Typeface chunkFiveFont = Typeface.createFromAsset(getAssets(), "fonts/chunkfivettfversion.ttf");
tvStats.setTypeface(chunkFiveFont);
TextView tvReading = (TextView)findViewById(R.id.txtReadingTitle);
tvReading.setTypeface(chunkFiveFont);
comment = null;
}
if(needData){
getLatestReadings();
}
populateGraph();
}
populateGraph
public void populateGraph(){
if(landScape){
graph_container = (LinearLayout)findViewById(R.id.graph_land_content_layout);
}else{
graph_container = (LinearLayout)findViewById(R.id.graph_content_layout);
}
//Create graphlayout
mainGraph_Layout = new RelativeLayout(this);
RelativeLayout.LayoutParams glParams = new RelativeLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
mainGraph_Layout.setId(909);
mainGraph_Layout.setLayoutParams(glParams);
graph_container.addView(mainGraph_Layout);
graph_container.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
if(needsGraph){
layoutGraph();
needsGraph = false;
}
}
});
}
layoutGraph
public void layoutGraph(){
viewWidth = mainGraph_Layout.getWidth();
viewHeight = mainGraph_Layout.getHeight();
//MORE STUFF IS HERE BUT NOT IMPORTANT
}
onConfigurationChanged
#Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
ActionBar actionBar = getActionBar();
if(newConfig.orientation==Configuration.ORIENTATION_LANDSCAPE){
//Config is landscape here
actionBar.hide();
needData = false;
landScape = true;
needsGraph = true;
constructLayout();
}else{
//Config is portrait here
actionBar.show();
needData = false;
landScape = false;
needsGraph = true;
constructLayout();
}
}
After rotation, it is at the layoutGraph() viewWidth and viewHeight objects where I have the problem. I had assumed by that point (having used the global layout listener) that the values would be correct. My understanding was that the listener would only have been triggered once "graph_container" was completed (and landscape or portrait) and so when calling layoutGraph() the width and height of "mainGraph_layout" (a child a graph_container, widths and heights set to MATCH_PARENT) would be good to go. It appears that the width and height I am getting are as if the phone is still portrait, and worth noting it appears that the removal of the action bar has also been taken into account.
Sorry for the long question but I thought it best to show all the code. If anything else needs to be shown then please let me know.
Thanks in advance,
Josh
There is a much better way to do this.
Use resource folders
Put your default layout files in res/layout, and the ones for landscape in res/layout-land. In other words, move res/layout/activity_bg_history_static_land.xml to res/layout-land/activity_bg_history_static.xml.
In onCreate, call
setContentView(R.layout.activity_bg_history_static);
The system will pick the file from res/layout-land when you are in landscape orientation, res/layout otherwise.
If you have views that are only present in one layout but not the other e.g. the comment button, wrap the code inside a null check like this:
btnComment = (Button) findViewById(R.id.btnCommentGraph);
if (btnComment != null) {
btnComment.setOnClickListener(this);
}
For populateGraph(), make sure both res/layout/activity_bg_history_static.xml and res/layout-land/activity_bg_history_static.xml has android:id="#+id/R.id.graph_content. Then you can do findViewById(R.id.graph_content) and get the LinearLayout you need.
Save data across rotation
In your activity, override onSaveInstanceState(), and save the data from getLatestReadings() into the bundle.
Then, in onCreate:
if (savedInstanceState == null) {
getLatestReadings();
} else {
// Restore latest readings from savedInstanceState
}
With that, you can let the system handle the rotation i.e. remove this from your manifest:
android:configChanges="keyboardHidden|orientation|screenSize"
Since the system is handling the rotation, you don't need to have a view tree observer any more. And you don't have to override onConfigurationChanged.
I want to make number of rows in Gridview dynamic depending upon the screen size of the android phone. so i need height of the Gridview in onCreate() to count number of rows.(height / colums) here is a code snippet::
public class GridActivity extends Activity
{
GridView gridView;
GridAdapter adapter;'
public void onCreate(Bundle savedInstance state)
{
setContentView(R.layout.gridview);
gridView = (GridView) findViewById(R.id.grid);
adapter = new GridAdapter(this);
gridView.setAdapter(adapter);
int gridHeight = gridView.getMeasuredHeight();
Log.d("tag"," height :: " + gridHeight);
}
}
In log cat it shows -- height :: 0
I don't understand y it gives zero after properly inflating.
Is this a bug? Or I am missing something?
GridView would return 0 measuredheight and 0 height unless it is drawn to window, so you can not get height of GridVIew at the point, A workaround of your problem may be:
Add your Nth view to gridView by comparing total height of device with height of GridView occupied after n-1 views has been drawn to screen.
public void addNCompareHeight(View view)
{
ViewTreeObserver observer = grid.getViewTreeObserver();
observer.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
//in here, place the code that requires you to know the dimensions.
//this will be called as the layout is finished, prior to displaying.
if(gridHeight<requiredHeight)
{
grid.addView(view);
addNCompareHeight(next);
}
}
}
I am not sure how you are going to relate your mobile screen size and GridView heigth. But here are the codes for finding out Screen resolution and GridView height.
1)To Find screen resolution do this in your onCreate(),
DisplayMetrics dm = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(dm);
int screen_width = dm.widthPixels;
int screen_height = dm.heightPixels;
2)To find gridView height you can use the below method,
#Override
public void onWindowFocusChanged(boolean hasFocus)
{
// TODO Auto-generated method stub
super.onWindowFocusChanged(hasFocus);
System.out.println("...111Height..."+gridview.getMeasuredWidth());
}
In my application, whenever the orientation of my screen changes, I want to display different images that will fit the screen. How can I do that?
Implement the method onConfigurationChanged in your Activity, there you can check the orientation and change the UI's look.
The parameter: newConfig.orientation will tell you the current orientation.
Try something like:
#Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
ImageView header = (ImageView) this.findViewById(R.id.header);
if ( newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE ) {
header.setImageResource(R.drawable.header480);
}
else if ( newConfig.orientation == Configuration.ORIENTATION_PORTRAIT ) {
header.setImageResource(R.drawable.header320);
}
}
You can also use this kind of code to actually get the screen size in real time:
Display display = ((WindowManager) getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
int width = display.getWidth();
...