I've got three views in my activity in a linear vertical layout. The top and bottom views have fixed heights and the middle view takes whatever height is left available. This is how I set the sizes for the views:
void resize(int clientHeight)
{
int heightMiddle = clientHeight - heightTop - heightBottom;
topView.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.FILL_PARENT, heightTop));
middleView.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.FILL_PARENT, heightMiddle));
bottomView.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.FILL_PARENT, LinearLayout.LayoutParams.FILL_PARENT));
}
In order to obtain the clientHeight, I overrode the onMeasure() function and call resize() inside my overridden onMeasure() function. This works well in onCreate(). However when the phone orientation changes, it does not work. What I observed is that after onCreate(), onMeasure() is called twice. After onConfigurationChanged(), onMeasure() is only called once and my resizing code does not get a chance to take effect. My kluge solution is to setup a timer to call resize() 20ms later:
timer.schedule(new TimerTask()
{
#Override
public void run()
{
activity.runOnUiThread(new UiTask());
}
}, 20);
where UiTask will simply call resize(). This works for me, but I feel that there's got to be a better solution. Can someone shed some light on this?
Why not let LinearLayout do the layouting with the help of the android:layout_weight attribute? That way, you don't need any extra code at all, everything will just work. Here's what your res/layout/main.xml could look like:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<TextView
android:layout_width="match_parent"
android:layout_height="50dp"
android:text="Top"
android:layout_weight="0"
android:background="#ff0000"
/>
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="Middle"
android:layout_weight="1"
android:background="#00ff00"
/>
<TextView
android:layout_width="match_parent"
android:layout_height="25dp"
android:text="Bottom"
android:layout_weight="0"
android:background="#0000ff"
/>
</LinearLayout>
And with no more code other than the regular
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
it would look like this in portrait:
and like this in landscape (automatically relayouted and redrawn):
This works both with and without android:configChanges="orientation" for the activity in the manifest. You'll also be able to setup the above layout using Java code if you need to.
Related
I have a two RelativeLayout where I have set their visibility to gone using,
android:visibility="gone"
And then on the root element of the activity which is also a RelativeLayout, I have added this attribute
android:animateLayoutChanges="true"
Here's my activity java code
RelativeLayout rellay1, rellay2;
Handler handler = new Handler();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
rellay1 = findViewById(R.id.rellay1);
rellay2 = findViewById(R.id.rellay2);
handler.postDelayed(new Runnable() {
#Override
public void run() {
rellay1.setVisibility(View.VISIBLE);
rellay2.setVisibility(View.VISIBLE);
}
}, 2000);
}
When I run the app, the views get displayed immediately without any wait. If I increase the time to 5s, still same results.
I've looked at the logs when starting the activity and nothing suspicious.
NOTE
When answering questions on SO, I sometimes find it hard to understand the users problem when it's cluttered with XML, so I tried hard to keep my question clean. But that might not be enough to diagnose what's wrong with my code so I'm adding a minimal version of the xml for the parent and two child RelativeLayouts without their contents, just the attributes. If more detail is needed I'd be happy to provide.
<?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:animateLayoutChanges="true"
android:background="#drawable/grad_bg"
tools:context=".LoginActivity">
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:layout_marginRight="40dp"
android:layout_marginLeft="40dp">
<ImageView
android:id="#+id/img_view_logo"
android:layout_width="80dp"
android:layout_height="80dp"
android:src="#drawable/ic_android_black_24dp"
android:adjustViewBounds="true"
android:scaleType="fitCenter"/>
<RelativeLayout
android:id="#+id/rellay1"
android:layout_below="#+id/img_view_logo"
android:layout_width="wrap_content"
android:visibility="gone"
android:layout_height="wrap_content">
</RelativeLayout>
</RelativeLayout>
<RelativeLayout
android:id="#+id/rellay2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp"
android:visibility="gone"
android:layout_alignParentBottom="true">
</RelativeLayout>
</RelativeLayout>
From the XML above, you'll see that my intention is to have only the ImageView visible initially and the rest should become visible after 2s.
I have looked similar questions about Handler#postDelayed but the problems are not exactly like mine so the solutions where not applicable.
UPDATE
The only problem here seems to be my lack of proper understanding of the android lifecycle(I'm a beginner here). I carried out an experiment by killing all running apps and tried launching my app again and I saw the animation.
So I guess what was happening here was that, the view had already been created and since my activity was still alive even though I had exited the app, subsequent launches of the App loaded a previously saved instance of the activity and that is why it seemed as if the animation wasn't working.
Having a layout which has horizontally side by side A and B parts when in landscape mode. Let's say A take 1/3 of the screen and B take other 2/3.
When rotate what is wanted is that the A keeps its original width but is changed to overlay on top of the B and B changes to have width of full screen underneath the A.
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<FrameLayout
android:id="#+id/left_part"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="0.3" />
<FrameLayout
android:id="#+id/right_part"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="0.7" />
</LinearLayout>
The part A, and B are holders for different fragments, which has it's adapetr with cursor etc. and could have a few stacked up in backstack. So when rotate would prefer to not re start the activity so that the context is maintained, but just some how to rearrange the layout dynamically.
Not sure if it is doable. Any suggestion?
Thanks!
Handling the Configuration Change Yourself
Your activity should be using saved instance state to persist the data from the old layout and restore it into the new layout.
It's theoretically possible to define a single layout that has sub groups from the different orientations, populate them both, then show / hide those as the rotation occurs using the method mentioned above for handling it yourself, but you are better off sticking Android's stock mechanism to handle this.
seems it should work with dynamically changing the layout, tried following shows the expected behavior.
Please comment if seeing any issue with this approach or having better solution. Thanks!
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<FrameLayout
android:id="#+id/right_pane"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignParentRight="true"
android:layout_alignParentLeft="false"
android:layout_toRightOf="#+id/left_part" />
<FrameLayout
android:id="#+id/left_part"
android:layout_width="#dimen/left_part_width"
android:layout_height="match_parent"
android:layout_alignParentLeft="true"/>
</RelativeLayout>
void doChangeMode() {
RelativeLayout.LayoutParams leftLp = (RelativeLayout.LayoutParams) leftPart.getLayoutParams();
RelativeLayout.LayoutParams rightLp = (RelativeLayout.LayoutParams) rightPart.getLayoutParams();
if (mode == mode_side_by_side) {
mode = mode_overlap;
rightLp.addRule(RelativeLayout.ALIGN_PARENT_LEFT);
} else {
mode = mode_side_by_side;
rightLp.addRule(RelativeLayout.ALIGN_PARENT_LEFT, 0);
//api 17 above
//rightLp.removeRule(RelativeLayout.ALIGN_PARENT_LEFT);
}
Handler delayHandler = new Handler();
delayHandler.post(new Runnable() {
#Override
public void run() {
rootView.requestLayout();
}
});
}
I created a custom component called ScrollListView, which is basically a table, extending ListView. Using the function getView below, I fill this component with the data coming from the database. It works perfectly and the result is visually that:
Obviously I would like the cells were aligned, like this:
To achieve this, currently I perform the calculations to measure all the cell sizes, and then adjust the column based on the widest cell, but GC is called several times, causing lag during scrolling, as previously reported is this thread
My question is: how to automatically fix/measure the size of the columns, without crazy calculations called in every getView call, extinguishing all those GC occurrences? I've tried to extend the GridView instead ListView, but it did not work. I´m new on Android.
(I CAN NOT use a standard component, such as GridView or GridLayout, I MUST use my custom component ScrollListView due to other more complex functions that currently operate correctly)
Thanks in advance and sorry for my bad english
Edit: Here is my code (the getView code is in the thread already mentioned):
scroll_listview.xml
<?xml version="1.0" encoding="utf-8"?>
<HorizontalScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center_horizontal"
android:background="#color/BackGroundColor"
android:scrollbars="none" >
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center_horizontal">
<TableLayout
android:id="#+id/header_lv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal">
</TableLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_horizontal"
android:orientation="horizontal" >
<android.ListView
android:id="#+id/rows_lv"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="0dip"
android:divider="#null"
android:dividerHeight="0dp"
android:padding="0dip"
android:scrollbars="horizontal"
android:gravity="center_horizontal"
android:stretchColumns="*"
/>
</LinearLayout>
</LinearLayout>
</HorizontalScrollView>
And my ScrollListView.java
public class ScrollListView extends LinearLayout
{
TableLayout header;
ListView rows;
public ScrollListView(Context context, AttributeSet attrs)
{
super(context, attrs);
LayoutInflater layoutInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View view=layoutInflater.inflate(R.layout.scroll_listview,this);
if (!this.isInEditMode())
{
rows = (ListView)this.findViewById(R.id.rows_lv);
header = (TableLayout)this.findViewById(R.id.header_lv);
rows.setOnRedrawListener(new OnListViewRedraw() {
#Override
public void onBeforeRedraw() {
}
#Override
public void onAfterRedraw() {
}
});
rows.setHeader(header);
rows.setMyParent(this);
}
// (and so on...)
Check out the TableLayout.onMeasure() source code. They find the widths of the cells for all children, then adjusts all children to match accordingly. You will need some modified version of this to fit your needs.
I am just learning about android development, and I am having some issues with getting this to work.
I have an activity that uses a relatively layout. I need it to have 2 buttons along the bottom, and then right above the bottoms, I want my custom view to take up the rest of the space.
viewer.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/viewerLayout"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<sketchViewer.AnimationPanelView
android:id="#+id/animationView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_above="#+id/homeFromViewerButton"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true" />
<Button
android:id="#+id/homeFromViewerButton"
android:layout_width="640dp"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentLeft="true"
android:text="Replay" />
<Button
android:id="#+id/replayButton"
android:layout_width="640dp"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentRight="true"
android:text="Home" />
</RelativeLayout>
The issue I am having is I that when I run my program, I need to pass a number of parameters into my custom view constructor so that my custom view decides what it should draw. So after creating an instance of my custom view (AnimationPanelView), I am not sure how I set this object into the space I provided for the view.
This is my activity class:
Viewer.java
public class Viewer extends Activity {
AnimationPanelView animationPanelView;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_viewer);
animationPanelView = new AnimationPanelView(this, true /*, more parameters here */);
animationPanelView.setBackgroundColor(Color.GREEN);
RelativeLayout v = (RelativeLayout) findViewById(R.id.viewerLayout);
v.addView(animationPanelView);
}
Right now, with my v.addView command, the view takes up the entire page, covering up the buttons at the bottom. Can anyone shed some light on this? I feel like I am close, but I've been playing around with it for a while, and I just seem stuck.
Check out the implementing a custom view section here. You need to override onLayout and onMeasure so you can tell your container how big you are.
You are adding another custom view to your layout instead you should use
animationPanelView = (AnimationPanelView) findViewById(R.id.animationView);
animationPanelView.setBackgroundColor(Color.GREEN);
I'm having some trouble with detecting screen clicks on the GUI. Works in portrait but fails in landscape, see below.
I have a GUI (Fragment) which contains some instructions + images. The user is required to tap anywhere on the screen to proceed. In order capture the click/tap event, I have put in a View(topview) that fill the entire screen and sits onto of other elements, I then listen for clicks on this view and it works fine.
The problem is when in landscape mode, the text and images take up to much room. So the whole thing is now wrapped in a ScrollView. This is where the problem begins. When the ScrollView is active, (i.e. you can scroll/scroll bars are visible), my view on top (topview) disappears. It seems that when in landscape mode the height of content in a ScrollView is being changed. As an experiment I replaced the View with a Button and the Button goes from filling the screen in portrait to being normal height in landscape mode when the ScrollView is usable.
Is there a way of me detecting the user tapping on the screen, which works with the ScrollView control as the top element. I've tried rearranging the GUI in several ways but without success, and I've tried adding onClick event handlers to the ScrollView, also without success.
My Layout is below, note my top view is semi-transparent red, so I could see the area it covered.
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true"
android:clickable="true" >
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:orientation="vertical" >
<TextView
android:id="#+id/txtInstructions"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_horizontal|center_vertical"
android:padding="10dp"
android:text="#string/instructions"
android:textColor="#color/blue"
android:textSize="20sp" />
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:adjustViewBounds="true"
android:maxWidth="250dp"
android:padding="20dp"
android:src="#drawable/main_camera" />
</LinearLayout>
<View
android:id="#+id/view_to_listen_for_touch"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="#88FF0000"
android:clickable="true" />
</RelativeLayout>
One thing that works(although looks like more like a hack(pretty ugly)) is to programatically add the special View in code(in the onCreate method) and set its dimensions based on the parent RelativeLayout's exact dimensions. Here is a snippet of code:
//...
final RelativeLayout parent = (RelativeLayout) findViewById(R.id.ff);
final View layer = new View(this);
layer.setBackgroundColor(Color.parseColor("#88FF0000"));
// the ScrollView really doesn't like this View ,using this without the
// runnable will not work
layer.setLayoutParams(new RelativeLayout.LayoutParams(
RelativeLayout.LayoutParams.MATCH_PARENT,
RelativeLayout.LayoutParams.MATCH_PARENT));
layer.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
Toast.makeText(getApplicationContext(), "SDFD",
Toast.LENGTH_SHORT).show();
}
});
parent.addView(layer);
// this is required because if we use directly the getWidth/getHeight we
// will get 0/0
layer.post(new Runnable() {
#Override
public void run() {
layer.setLayoutParams(new RelativeLayout.LayoutParams(parent
.getWidth(), parent.getHeight()));
}
});
//...