I have an Android Layout like this :
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/main_layout"
android:orientation="vertical">
<android.support.v7.widget.RecyclerView
android:id="#+id/rv_weighing"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"/>
<android.support.v7.widget.CardView
xmlns:card_view="http://schemas.android.com/apk/res-auto"
android:id="#+id/cv_price_holder"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<!-- another view elements here -->
</android.support.v7.widget.CardView>
</LinearLayout>
Basically I want the first element fill the remaining space between parent and the second element. It works fine until I change the Y position of the second element at runtime. I tried to call .invalidate() and .requestLayout() on both parent and the first element but it seems like doesn't work as expected.
I also tried to change the parent layout using <RelativeLayout> and set android:layout_above="#+id/cv_price_holder" on the first element but didn't work as well.
EDIT : Here is the method that invoked to toggle the layout :
public void togglePriceHolder(){
float newY = mPriceHolderToggleState ? -mPriceHolderToggleLimitY : mPriceHolderToggleLimitY;
int newIcon = mPriceHolderToggleState ? R.drawable.ic_keyboard_arrow_down_white_36dp : R.drawable.ic_keyboard_arrow_up_white_36dp;
mBtnTogglePriceHolder.setImageResource(newIcon);
mCvPriceHolder.animate().translationYBy(newY).setListener(new Animator.AnimatorListener() {
#Override
public void onAnimationStart(Animator animation) {}
#Override
public void onAnimationEnd(Animator animation) {
// refresh the layout
rvWeighing.forceLayout();
mMainLayout.requestLayout();
}
#Override
public void onAnimationCancel(Animator animation) {}
#Override
public void onAnimationRepeat(Animator animation) {}
});
mPriceHolderToggleState = !mPriceHolderToggleState;
}
The question is how can I refresh the layout so the first element can adjust its height when Y position of the second element change ?
Thanks in advance
Try calling forceLayout() on the views whose dimensions might change and then calling requestLayout().
When requestLayout() is called on a view, all the parents of that view are redrawn, but the siblings are not, calling forceLayout() on a particular view, marks that view for the next layout pass(along with the default traversal).
In your particular case, try calling forceLayout() on the siblings you want to redraw, and then call requestLayout() from one of the views.
Related
I have a recyclerView inside nestedScrollView. I know it's a bad practice, but there are some cases, that I can handle only in this way.
<androidx.core.widget.NestedScrollView
android:id="#+id/nestedScroll"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/recycler_data"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:itemCount="1"
tools:listitem="#layout/item_favorite_tournament"
tools:visibility="visible" />
</androidx.core.widget.NestedScrollView>
And here's my code. I'll show you the scroll part, where I need to get the firstVisibleItemPosition. But it returns 0.
private void initList(RecyclerView recycler, View labelNoResults) {
recycler.setAdapter(mAdapter);
recycler.setHasFixedSize(true);
mListLayoutManager = Objects.requireNonNull((LinearLayoutManager) recycler.getLayoutManager());
recycler.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrollStateChanged(#NonNull RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
final int p = mListLayoutManager.findFirstVisibleItemPosition();
Log.d("Tag_1", "Position: " + p);
}
});
}
I think the problem is because of nestedScrollView. So how can I get the visibleItemPosition form recyclerView? Thank you.
The easiest way to fix this would be to replace NestedScrollView by simply ScrollView. This of course will change the behavior of your layout, but it will show you the right position.
In case of NestedScrollView, findFirstVisibleItemPosition() always returns 0 and findLastVisibleItemPosition() always returns the last item in the RecyclerView despite whether they are visible to the user or not. This happens because RecyclerView placed inside NestedScrollView behaves like a ListView without doing any recycling. Meaning all items are visible at the same time because all of them are bind at the same time.
I'm trying to make a custom NumberPicker to display in a DialogFragment. So far I've succeeded in getting the picker to display in a dialog fragment and getting it to display the custom strings I want it to. I've also disabled the descendantFocusability so the text is not editable. Here is an overview of the questions I have about NumberPicker behaviour, I'll go more in depth after:
How does one 'commit' their selection?
How to return the selected value?
How does one 'commit' their selection?
When the dialog appears, I don't see a clear way to 'select' an option (see image below). Looking at native Android selection dialogs, I often see radiobuttons. Is that the way to go? And am I using the wrong UI component to build this?
How to return the selected value?
This question is tightly knit with the last one, as not knowing how to commit a selection obviously doesn't help here. Right now I use NumberPicker.OnValueChangeListener to see if the value changed, however it never fires. Here's how I structured the code:
class PlatePickerFragment: DialogFragment() {
lateinit var listener: NumberPicker.OnValueChangeListener
//I set up the fragment with onCreateDialog here.
}
And this is the code I use when I create an instance:
val platePicker = PlatePickerFragment()
platePicker.listener = NumberPicker.OnValueChangeListener { numberPicker, i1, i2 ->
//set what to do on value change here.
}
However, this block never gets called.
TL;DR: Am I using the right UI component? If I am, how would I implement this in a way that it works? Why does the NumberPicker not have a cancel/ok section by default (see image of DatePicker below)? Thanks in advance!
Answer to first part :
This is the ideal way of implementing NumberPicker. One thing you can do
is add an OK button to side to catch selection.See screenshot
Code for same :
picker.xml
<?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">
<RelativeLayout
android:id="#+id/rl"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#ffffff"
android:padding="16dp">
<TextView
android:id="#+id/tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:padding="5dp"
android:text="OK"
android:textSize="15dp" />
<NumberPicker
android:id="#+id/numberPicker"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="#id/tv"
android:layout_marginTop="10dp" />
</RelativeLayout>
</LinearLayout>
In your activity :
final NumberPicker aNumberPicker = (NumberPicker) dialog.findViewById(R.id.numberPicker);
aNumberPicker.setMaxValue(12);
aNumberPicker.setMinValue(1);
aNumberPicker.setValue(1);
aNumberPicker.setFocusable(true);
aNumberPicker.setFocusableInTouchMode(true);
aNumberPicker.setOnScrollListener(new NumberPicker.OnScrollListener() {
#Override
public void onScrollStateChange(NumberPicker view, int scrollState) {
value = view.getValue();
}
});
aNumberPicker.setOnValueChangedListener(new NumberPicker.OnValueChangeListener() {
#Override
public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
value = newVal;
}
});
TextView ok = (TextView) parent.findViewById(R.id.tv);
ok.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// value variable can be used here
}
});
Declare value as global variable.
Answer to the second part of your question :
int hour;
numberPicker.setOnValueChangedListener(new NumberPicker.OnValueChangeListener() {
#Override
public void onValueChange(final NumberPicker numberPicker, final int i, final int i1) {
hour = Integer.valueOf(numberPicker.getDisplayedValues()[numberPicker.getValue()]);
}
});
On clicking of OK button you will have answer in hour variable.
To a father layout I am adding successive child layouts in the following way
Parent layout (parent.xml)
<LinearLayout
android:id="#+id/ly_parent"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<!-- In principle, the cardview should not interfere with the task.
I put the code as a precaution. -->
<android.support.v7.widget.CardView
android:id="#+id/media_card_view"
...>
<LinearLayout
android:id="#+id/ly_wrapper"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<!-- First row: header -->
<LinearLayout
android:id="#+id/row_1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
... />
<TextView
... />
</LinearLayout>
<!-- Second row: details -->
<!-- Programmatically included -->
</LinearLayout>
</android.support.v7.widget.CardView>
</LinearLayout>
Child layout (child.xml)
<LinearLayout
android:id="#+id/child"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
... />
<ImageView
android:id="#+id/iv_image"
... />
</LinearLayout>
Layouts are nested in a function called createView(), in this function I explain my doubt
private void createView() {
LinearLayout parent = (LinearLayout) inflater.inflate(R.layout.parent, (ViewGroup) findViewById(R.id.ly_parent));
LinearLayout wrapper = parent.findViewById(R.id.ly_wrapper);
LinearLayout child = (LinearLayout) inflater.inflate(R.layout.child, (ViewGroup) findViewById(R.id.ly_child));
wrapper.addView(child);
ImageView imageView = parent.findViewById(R.id.iv_image);
//So far everything works correctly
imageView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
//In this event I need to add a new child (ly_child).
//The iv_image field of the newly inserted child must create another child,
//and so on, Something like this:
wrapper.addView(child);
//Get the new ImageView and create the onClickListener event,
//this process will be repeated an indeterminate number of times.
//I had managed to add the children's layout but after some tests I
//modified something and the code fails :(, throws the following exception:
//"The specified child already has a parent. You must call removeView() on the child's parent first."
//I have tried to put a different id to the wrapper layout within the
//onClickListener event but it still does not work. Something like this:
//child.setId(); <- Generation of a unique id.
//wrapper.addView(child);
}
});
}
A summary could be: I need to add rows (each row is a child layout -child.xml-) and the ImageView of
each row must have its onClick event to create another line. This is repeated indefinitely.
Any ideas? I'm a bit blocked
You'll need to change this code as below.
LinearLayout parent;
LinearLayout wrapper;
private void createView() {
parent = (LinearLayout) inflater.inflate(R.layout.parent, (ViewGroup) findViewById(R.id.ly_parent));
wrapper = parent.findViewById(R.id.ly_wrapper);
addNewChild();
}
private void addNewChild(){
//update this below line
LinearLayout child = (LinearLayout) inflater.inflate(R.layout.child,null,false);
ImageView imageView = child.findViewById(R.id.iv_image);
wrapper.addView(child);
imageView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
addNewChild();
}
});
}
This will create single child on each onClick event and you can set unique id and code execution for each child differently.
I have a ViewPager implemented without fragment (just view). Each page contains a list of elements, but the list hasn't always the same size.
I tried to set the ViewPager dimension with this code (extends ViewPager so I can override this function) :
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int height = 0;
for(int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
child.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
int h = child.getMeasuredHeight();
if(h > height) height = h;
}
heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
The problem is that it set the same height for every page. I need a custom height for each page, depending of the height of the list.
I also tried to use setOnPageChangeListener calling requestLayout() and invalidate() on the ViewPager, but the size remains the same. Maybe I did it wrong...
Here is some code that might be useful :
A part of my PagerAdapter:
#Override
public Object instantiateItem(ViewGroup container, int position) {
// Inflate a new layout from our resources
View view = getActivity().getLayoutInflater().inflate(R.layout.fragment_home_pager,
container, false);
mContainerView = (ViewGroup) view.findViewById(R.id.news_container);
reloadNews(position);
// Add the newly created View to the ViewPager
container.addView(view);
// Return the View
return view;
}
A part of my XML layout:
<ScrollView
android:id="#+id/news_scrollview"
android:layout_width="match_parent"
android:layout_height="fill_parent">
<com.ViewPagerWithHeight
android:id="#+id/viewpager"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#android:color/white"/>
</ScrollView>
Thanks of lot for helping me,
The ViewPager height cannot vary per-page as you are hoping. You could attempt to subclass ViewPager to add this ability, but handling the visual transitions between differently-sized pages is going to be very fiddly. Another alternative may be to re-measure the ViewPager on every page change (but I would expect the transition to look pretty bad). You would need to set a page change listener, then call requestLayout on the ViewPager. Your ViewPager onMeasure would need to find the height of the currently-visible child only, and set the ViewPager height to match this.
Aside: I don't see you calling setMeasuredDimension anywhere. When measuring a view, you must call this method with your calculated width and height to inform the view what size it has been measured at. Currently you are calling through to super.onMeasure at the end of your custom onMeasure which, while valid, means the ViewPager will always be measured with the "default" width and height. In this case, that happens to match the height of the largest page anyway.
See the docs for onMeasure for more details. In particular:
CONTRACT: When overriding this method, you must call setMeasuredDimension(int, int) to store the measured width and height of this view. Failure to do so will trigger an IllegalStateException, thrown by measure(int, int). Calling the superclass' onMeasure(int, int) is a valid use.
I just find out something that do the job.
I moved the Scrollview inside the pageItem.xml so I can set the page size (for all pages) but still can scroll if the list is long enough. The ViewPager is now inside a FrameLayout with layout_height = fill_parent.
The page size is fixed, but the scrollview do the trick...
The ViewPager (inside my fragment) :
<FrameLayout
android:layout_width="match_parent"
android:layout_height="fill_parent"
android:id="#+id/viewpagercontainer">
<com.ViewPagerWithHeight
android:id="#+id/viewpager"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#android:color/white"/>
</FrameLayout>
The "Page item" :
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/news_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:showDividers="middle"
android:divider="?android:dividerHorizontal" />
</ScrollView>
onMeasure function:
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
heightMeasureSpec = MeasureSpec.makeMeasureSpec(containerHeight, MeasureSpec.EXACTLY);
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
How to set the attribute containerHeight (more info here):
final View pagerContainer = (View) mView.findViewById(R.id.viewpagercontainer);
pagerContainer.post(new Runnable() {
#Override
public void run() {
mViewPager.setContainerHeight(pagerContainer.getMeasuredHeight());
}
});
I'm having to use TabHost in place of ActionBarTabs and to make them scroll-able I've wrapped my TabWidget in a HorizontalScrollView, but the HorizontalScrollView doesn't scroll itself in accordance with ViewPager. I've tried using scrollTo and fullScroll in a couple of different ways, but it doesn't change anything. What do I need to do to get this working correctly?
<TabHost xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#android:id/tabhost"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<HorizontalScrollView
android:id="#id/horizontalScrollView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fillViewport="true"
android:scrollbars="#null" >
<TabWidget
android:id="#android:id/tabs"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal" />
</HorizontalScrollView>
<FrameLayout
android:id="#android:id/tabcontent"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_weight="0" />
<android.support.v4.view.ViewPager
android:id="#id/viewPager"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
</LinearLayout>
#Override
public void onTabChanged(String tabId) {
int position = mTabHost.getCurrentTab();
mViewPager.setCurrentItem(position);
mHorizontalScrollView.scrollTo(position, 0);
}
#Override
public void onPageScrolled(int position, float positionOffset,
int positionOffsetPixels) {
mHorizontalScrollView.scrollTo(position, 0);
}
The horizontal scroll view will change when you call .refreshDrawableState() after calling the .scrollTo(x,y) method.
Another thing to watch out for is that .scrollTo(x,y) scrolls such that x is positioned on the left side of the screen. You may need to do some math with the coordinates of your tabs and the width of the horizontal scroll view to position things correctly. You can't call .scrollTo(position,0) and have it work the way you'd like (unless your tabs are 1 pixel wide).
ViewPager has the brains for the scrolling behavior.
I would remove the HorizontalScrollView from your layout. The rest looks fine.
I would then examine this Google provided code example, which I based a ViewPager interface like you want to accomplish on: http://developer.android.com/resources/samples/Support4Demos/src/com/example/android/supportv4/app/FragmentTabsPager.html
You'll notice that the corresponding methods that you declared above don't have anything to do with scrolling, other than to set which page to scroll to. ViewPager handles the smooth scrolling internally.
#Override
public void onTabChanged(String tabId) {
int position = mTabHost.getCurrentTab();
mViewPager.setCurrentItem(position);
}
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}