I currently have a ScrollView with a LinearLayout. I'm changing some content dynamically, e.g. setting a long text passage dynamically, and adding dynamic layouts (buttons etc.). This causes the linear layout's height to change, which causes the ScrollView to scroll down. How can I keep the current scroll position while the layout is being dynamically updated?
<ScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical
android:gravity="center_horizontal"
>
<!-- Dynamic Content Here -->
</LinearLayout>
</ScrollView>
One option i would suggest is to save the scroll position in a bundle and restore it.
#Override
protected void onSaveInstanceState(Bundle outState) {
outState.putInt("xPos", scrollView.getScrollX());
outState.putInt("yPos", scrollView.getScrollY());
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
int scrollX = savedInstanceState.getInt("xPos"); // Default value 0
int scrollY = savedInstanceState.getInt("yPos");
scrollView.scrollTo(scrollX, scrollY);
}
I think with this steps you can do it :
Create a custom scroll view that extends from scrollview
Define a boolean property for enabling and disabling scroll
Check the boolean variable on the onScollChanged method and return false if is true
Set the boolean variable true before adding dynamic views to it's container
Set it to false when adding done
I hope these help you
Related
I am trying to implement Bottom sheet in one of my activities and I am kind of confused by the way it is behaving!
So here is the problem, I have an activity in which I am trying to show Bottom sheet and I see that:
if we dont set the app:behavior_peekHeight property then the Bottom sheet never works
If you set the PeekHeight to something less than 30dp (basically just to hide it from screen)
If you set app:behavior_peekHeight to more than 30dp in layout file and try to set the state of bottomSheetBehavior to STATE_HIDDEN in you onCreate method your app crashes with this error
caused by:
java.lang.NullPointerException: Attempt to invoke virtual method
'java.lang.Object java.lang.ref.WeakReference.get()' on a null object reference at android.support.design.widget.BottomSheetBehavior.setState(BottomSheetBehavior.jav a:440)
at myapp.activity.SomeActivity.onCreate(SomeActivity.java:75)
I am really confused on why is it not allowing me to hide it in onCreate? or why cant we just set the peekHeight to 0 so that it is not visible on screen unless we call the STATE_EXPANDED or even not setting that property should default it to hide! or atleast I should be able to set it as hidden in my onCreate!
am I missing something? or is the behavior of the BottomSheet rigid?
my layout file for the BottomSheet is something like this:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:background="#android:color/white"
android:layout_height="100dp"
android:orientation="vertical"
app:behavior_hideable="true"
app:behavior_peekHeight="40dp" <!-- I cant set this less than 30dp just to hide-->
app:layout_behavior="#string/bottom_sheet_behavior"
tools:context="someActivity"
android:id="#+id/addressbottomSheet"
tools:showIn="#layout/some_activity">
in my activity I am doing something like this:
#InjectView(R.id.addressbottomSheet)
View bottomSheetView;
#Override
protected void onCreate(Bundle savedInstanceState) {
....
bottomSheetBehavior = BottomSheetBehavior.from(bottomSheetView);
// only if I have set peek_height to more than 30dp
bottomSheetBehavior.setState(BottomSheetBehavior.STATE_HIDDEN);
}
In my onclick I am doing this:
#Override
public void onItemClick(View view, int position) {
bottomSheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED);
}
After working on this issue for few more days I found one alternate solution for this:
Instead of using the Bottom_sheet directly inside your layout, if we create a Bottom_Sheet fragment and then instantiate it in the activity this issue will not occur and the bottom sheet will be hidden and we dont need to specify the peek_height
here is what I did
public class BottomSheetDialog extends BottomSheetDialogFragment implements View.OnClickListener {
#Override
public View onCreateView(LayoutInflater inflater,
ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_bottom_sheet, container, false);
}
Then in my activity
bottomSheetDialog = BottomSheetDialog.newInstance(addressList.get(position), position);
bottomSheetDialog.show(getSupportFragmentManager(), AddressActivity.class.getSimpleName());
This actually solved my problem of bottom sheet being not hidden when the activity starts but I am still not able to understand why if bottom_sheet is included directly we face that problem!
(Referring to the question) Suzzi bro the issue with your code is you are trying to call the setState method directly inside onCreate. This is will throw a nullPointer because the WeakReference is not initialized yet. It will get initialized when the Coordinator layout is about to lay its child view.
onLayoutChild(CoordinatorLayout parent, V child, int layoutDirection)
Called when the parent CoordinatorLayout is about the lay out the
given child view.
So the best approach is set the peek height to 0 and show/hide inside the onItemClick listener.
Here is my code:
bottom_sheet.xml
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#ffffff"
android:gravity="center"
android:orientation="vertical">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="#mipmap/ic_launcher" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="Bottom sheet"
android:textColor="#android:color/black" />
</LinearLayout>
activity_main.xml
<Button
android:id="#+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Show hide bottom sheet" />
<include
android:id="#+id/gmail_bottom_sheet"
layout="#layout/bottom_sheet" />
MainActivity.java
public class MainActivity extends AppCompatActivity {
boolean isExpanded;
Button button;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
CoordinatorLayout coordinatorLayout = (CoordinatorLayout) findViewById(R.id.gmail_coordinator);
final View bottomSheet = coordinatorLayout.findViewById(R.id.gmail_bottom_sheet);
final BottomSheetBehavior behavior = BottomSheetBehavior.from(bottomSheet);
button = (Button) findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
if (isExpanded) {
behavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
} else {
behavior.setState(BottomSheetBehavior.STATE_EXPANDED);
}
isExpanded = !isExpanded;
}
});
}
}
Here initially the bottom sheet is not visible. On clicking the button we will be set the state to STATE_COLLAPSED/STATE_EXPANDED.
The tutorial I followed to make this demo app is listed below:
Bottom Sheet with Android Design Support Library
The reason its crashing is due to the fact that the weak reference is not being set until one of the last lines in onLayoutChild, which gives you your null ptr exception.
What you can do is create a custom BottomSheet Behavior and override onLayoutChild, setting the expanded state there.
An example can be found here:
NullPointerExeption with AppCompat BottomSheets
To avoid the Null pointer exception, set the state to HIDDEN like this in onCreate()
View bottomSheetView = findViewById(R.id.bottomsheet_review_detail_id);
mBottomSheetBehavior = BottomSheetBehavior.from(bottomSheetView);
bottomSheetView.post(new Runnable() {
#Override
public void run() {
mBottomSheetBehavior.setState(BottomSheetBehavior.STATE_HIDDEN);
}
});
When I perform an orientation change the scrollview sometimes repositions so I lose the title (e.g. Chips Of Cholcolates...) textview of my view.
Here is what I want.
When I perform an orientation change I lose the title view.
This is my scrollview layout.
<ScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_margin="0dp"
android:scrollbars="vertical"
android:fillViewport="true"
android:scrollbarAlwaysDrawVerticalTrack="true"
android:id="#+id/scrollview">
<LinearLayout
style = "#style/Activity"
android:keepScreenOn="false"
android:paddingBottom="0dp"
android:id="#+id/recipe_view" >
<TextView
android:id="#+id/name"
style="#style/name"
android:hint="#string/recipe_title"
/>
<TextView
android:id="#+id/instructions"
style="#style/instructions"
android:gravity="start"
android:hint="#string/recipe_instructions"
/>
</LinearLayout>
</ScrollView>
In the fragment's onCreateView() I'm calling
// correct the position of the scrollview
mView.findViewById(R.id.recipe_view).scrollTo(0, 0);
to no effect.
The problem seems to be random. Sometimes the scrollbar pushes the title out of view, sometimes it does not.
You can save and restore the scroll position when the orientation changes.
Save the current position in onSaveInstanceState:
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putIntArray("ARTICLE_SCROLL_POSITION",
new int[]{ mScrollView.getScrollX(), mScrollView.getScrollY()});}
And restore it in onRestoreInstanceState:
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
final int[] position = savedInstanceState.getIntArray("ARTICLE_SCROLL_POSITION");
if(position != null)
mScrollView.post(new Runnable() {
public void run() {
mScrollView.scrollTo(position[0], position[1]);
}
});}
--OR--
A non-hacky way would be to just put android:configChanges="keyboardHidden|orientation|screenSize" in that particular activity's tag in AndroidManifest.xml, and handle the activity recreation stuff yourself.
What i want in pictures:
On the words: i want to disable scrolling with formatting text inside HorizontalScrollView.
Part of XML:
<ScrollView
android:id="#+id/review_scrollview"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:scrollbars="horizontal"
android:fillViewport="true"
android:layout_marginTop="10dp">
<HorizontalScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<TextView
android:id="#+id/tt_review_content"
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:textAppearance="?android:attr/textAppearanceSmall"
android:text="Some long text"
android:scrollHorizontally="true"/>
</HorizontalScrollView>
</ScrollView>
Method(doesn't works):
private void setTtAreaWrapContent(boolean value) {
ViewGroup.LayoutParams params = textField.getLayoutParams();
if(value) { // Wrap content
textField.setWidth(scrollView.getWidth());
} else { // Scroll
params.width = ViewGroup.LayoutParams.WRAP_CONTENT;
}
textField.setLayoutParams(params);
}
There is a very simple way to do this.
Check if the user wants to scroll or not and then store it in a boolean variable, shouldScroll.
Now do this in onCreate,
HorziontalScrollView scrollView= (HorizontalScrollView)findViewById(R.id.scrollView);
if(!shouldScroll)
scrollView.setOnTouchListener(new OnTouch());
You also need to define a class OnTouch extending the OnTouchListener
private class OnTouch implements OnTouchListener
{
#Override
public boolean onTouch(View v, MotionEvent event) {
return true;
}
}
Now for the TextView formatting, just use android:singleLine="false" or use android:width="0dip"
I had absolutely the same need for code, so I initially proposed a ViewSwitcher with the same layouts, but one with HorizontalScrollView and one without it. And switch to the corresponding view depending on the setting.
However, I found an easier solution. After recreation of the Activity (when wrapping settings are changed by the user), use the code in the onCreate methode:
if (isWrapContent)
{
View mTV = findViewById(R.id.my_text_view);
HorizontalScrollView myHSV = (HorizontalScrollView) findViewById(R.id.my_hsv);
ViewGroup parent = (ViewGroup) myHSV.getParent();
ViewGroup.LayoutParams params = myHSV.getLayoutParams();
int index = parent.indexOfChild(myHSV);
myHSV.removeAllViews();
parent.removeView(myHSV);
parent.addView(mTV, index, params);
}
You need to use your id's and variables correspondingly.
It will remove the horizontal scrollview with the embedded textview and then reinsert the textview.
My answer provides direct and valid answer on how the asked issue could be resolved instead of freezing the horizontal scrollview or wondering about the point of the question, as it is done in the other comments and answers.
I have an activity which contains a ScrollView, and also I have a GridView inside the ScrollView, the layout:
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/root">
<RelativeLayout
android:padding="10dp"
android:layout_width="match_parent"
android:layout_height="match_parent">
.........
<com.test.android.view.ScrollableGridView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:numColumns="auto_fit"
android:columnWidth="100dp"
android:verticalSpacing="2dp"
android:focusable="false"
android:clickable="false">
</com.test.android.view.ScrollableGridView>
</RelativeLayout>
</ScrollView>
public class ScrollableGridView extends GridView {
#Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST);
super.onMeasure(widthMeasureSpec, expandSpec);
ViewGroup.LayoutParams params = getLayoutParams();
params.height = getMeasuredHeight();
}
}
Why I use the custome gridview is to make sure the gridview can expand to its max height(check this).
Now once the activity loaded, I will load data from the server, then call the:
gridAdapter.notifyDatasetChanged();
Then the activity will scroll to the grid view which means user can not see the content above the gridview.
I have tried that:
mScrollView.scrollTo(0,mScrollView.getBottom());
But it does not work.
Any idea to fix it?
Issue:
GridView is being scrolled automatically.
Reason:
When the screen is loaded, it finds the first focusable View from its ViewHierarchy and sets the focus to that View.
Solution:
You can set focus to some other View so that Android does not focus the GridView at first,so it won't scroll automatically.
Example:
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/root">
<RelativeLayout
android:padding="10dp"
android:id="#+id/rlContainer" //you may set id and requestfocus from java code as well
android:focusable="true" //add this
android:focusableInTouchMode="true" //add this
android:layout_width="match_parent"
android:layout_height="match_parent">
......... //you can also add focusable,focusableInTouchMode to any other view before the GridView
<com.test.android.view.ScrollableGridView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:numColumns="auto_fit"
android:columnWidth="100dp"
android:verticalSpacing="2dp"
android:focusable="false"
android:clickable="false">
</com.test.android.view.ScrollableGridView>
</RelativeLayout>
From java code,
inside onCreate() method of your Activity
RelativeLayout rlContainer = (RelativeLayout) findViewById(R.id.rlContainer);
rlContainer.requestFocus();
A better option will be to scroll to the GridView's top. Also, you should post the scrollTo(int, int) call:
mScrollView.post(new Runnable() {
#Override
public void run() {
mScrollView.scrollTo(0, mGridView.getTop());
}
});
Edit:
So, from what I can gather:
on first load, GridView is at the top & visible
then, you load some data from the server
some layout container above the GridView is updated with data - this increases the layout's size and the GridView is pushed down
Lets say that the layout container above the GridView is mLayoutContainer. After adding data to this container, add a OnPreDrawListener to it:
mLayoutContainer.getViewTreeObserver().addOnPreDrawListener(
new ViewTreeObserver.OnPreDrawListener() {
#Override
public boolean onPreDraw() {
// remove the OnPreDrawListener
mLayoutContainer.getViewTreeObserver().removeOnPreDrawListener(this);
// update scrollY for the ScrollView
// since mLayoutContainer is about to be drawn, its height
// is available.
mScrollView.setScrollY(mScrollView.getScrollY()
+ mLayoutContainer.getHeight());
// we're allowing the current draw pass
return true;
}
});
This is basically a state-restore operation. We are asserting that prior state was perfect - state changed resulting in the GridView being pushed down - counter state change by scrolling ScrollView by an equal amount.
I have an activity that has a ScrollView with a vertical LinearLayout that has two fragments that are PreferenceFragment instances as shown in the following layout file:
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_height="wrap_content"
android:layout_width="match_parent"
>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:context="com.foo.app.SettingsActivity">
<fragment
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:name="com.foo.app.SettingsFragment"
android:id="#+id/fragment_settings"/>
<fragment
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:name="com.foo.app.NotificationSettingsFragment"
android:id="#+id/fragment_notification_settings"/>
</LinearLayout>
The problem is that the two fragments show up with just their PreferenceCategory title and the actual fragment UI is zero height and not visible. Oddly, it is possible to scroll each fragment individually and see the missing fragment UI. It is as if each Fragment is inside a ScrollView.
What I expected was for the two fragments to be sized to wrap their content and there be a single vertical slider to scroll the LinearLayout containing both fragments.
In case it is relevant, the two fragments extends android.preference.PreferenceFragment and do not define their layout in a layout file. Instead they load their Preference UI as follows:
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Load the preferences from an XML resource
addPreferencesFromResource(R.xml.pref_notification);
}
TIA for your help.
Remove the outer ScrollView. I had the same problem and that solved it for me.
I haven't found it in the documentation yet, but apparently the PreferenceFragment supplies its own ScrollView around the PreferenceScreen. Somehow that leads to a wrap_content height just large enough to show the first element (e.g. a category header).
The question is quite old but in case someone else stumbles upon this, I've managed to find a solution. Just like #Ewoks mentioned, I had to settle for a fixed height as well (which didn't always play well with varying dpi of the devices).
But with the help of this code I managed to make the height dynamic, which wraps the content nicely.
In your PreferenceFragment class do as follows:
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if (getView() != null) {
ListView listView = (ListView) getView().findViewById(android.R.id.list);
Adapter adapter = listView.getAdapter();
if (adapter != null) {
int height = 0;
//int height = listView.getPaddingTop() + listView.getPaddingBottom();
for (int i = 0; i < adapter.getCount(); i++) {
View item = adapter.getView(i, null, listView);
item.measure(0, 0);
height += item.getMeasuredHeight();
}
FrameLayout frame = (FrameLayout) getActivity().findViewById(R.id.content_frame); //Modify this for your fragment
ViewGroup.LayoutParams param = frame.getLayoutParams();
param.height = height + (listView.getDividerHeight() * adapter.getCount());
frame.setLayoutParams(param);
}
}
}
Hope this helps someone!
Given that you can't remove the outer ScrollView, what worked for me is to change it to be a android.support.v4.widget.NestedScrollView and then in Activity's onCreate to run:
findViewById(R.id.nested_scroll_view).setNestedScrollingEnabled(false);
Try setting the the android:layout_weight in each fragment to roughly the size of each settings list. E.g.
<fragment
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="0.3"
android:name="com.foo.app.SettingsFragment"
android:id="#+id/fragment_settings"/>
<fragment
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="0.7"
android:name="com.foo.app.NotificationSettingsFragment"
android:id="#+id/fragment_notification_settings"/>