I'm working on an app that uses an expandable listview as its parent, and in each child view there is a horizontal scroll view that is loaded dynamically based on DB information.
All of the buttons in the HSV are compound images added dynamically using an implementation very similar to lazy loading in listview. I was able to add arrows to my HSV, but now I would like to take it one step further and only display arrows when there are enough items to fill the space, such that if only one item is showing then no arrows should be displayed.
Since a list starts in the beginning, I can hide the 'back' arrow by default, but I am unsure where to test whether or not to show the 'forward' arrow.
public Object getGroup(int groupPosition) {
Object o;
o = groups.get(groupPosition).getName();
if(relativeLayout != null && horizontalScrollView != null && forward != null) {
if(horizontalScrollView.getWidth() < relativeLayout.getWidth()) {
forward.setVisibility(View.VISIBLE);
} else {
forward.setVisibility(View.INVISIBLE);
}
}
// }
return o;
}
Originally I used this test in getChildView, where the HorizontalScrollView was populated, but it happened too soon and bot the HSV and relativelayout displayed a width of zero.
Right now I am testing it in getGroup, because it is called multiple times AFTER the childview has started to load, but I think this only works because I have a lot of groups in my expandable list. If I had one group I would still have the same problem.
Is there an ideal place to check whether or not the child layout is larger than the horizontalscrollview?
Update: I set a timer that checks every 1000 ms to see if forward should be turned visible and then exits. Its not pretty, but it works without crashing the app, so I'm happy. Sorry for the bad question!
I am checking the scroll position via the onTouch event:
horizontalScrollView.setOnTouchListener(actualScrollViewTouch);
Related
I want to change the height of all views within a recycler view at once. All items can have different heights (defined by their visible views, so not hard-coding possible).
With the press of a button, the heights of all items are changed to a smaller height. To do that, I'm looping through the recycler views and get every single one using findViewHolderForAdapterPosition(int position). This works fine for the visible items on the screen but not for the ones at the edge of the screen. It seems they are already drawn but not yet visible. As a result, findViewHolderForAdapterPosition will return nil for them. And I can't find a way to access them properly. I could use notifyItemChanged(int position) but this results in a weird animation.
Here's the simplified code I'm using to loop through the views and do the animations:
private var dataList = listOf<Any>() // Content list
fun animate(recycler: RecyclerView, isLarge: Boolean) {
this.isLarge = isLarge
for (it in dataList.indices) {
val v = recycler.findViewHolderForAdapterPosition(it)
if (v is ContentViewHolder) { // check type
if (isLarge) { // Transition isLarge -> small
v.binding.animateSmallToLarge(it) // changes visibility using GONE & VISIBLE
} else { // Transition short -> long
v.binding.animateLargeToSmall(it) // changes visibility using GONE & VISIBLE
}
}
}
}
Now I'm a super rookie on Android/Kotlin, so any hint will help! It seems that the animation that cases the views to reduce its size will make these "not accessible but drawn outside the screen" suddenly visible. How can I properly access them or invalidate them so they get redrawn (NOT using notifyItemChanged)? Any ideas?
I'm doing a scrollable list of values.
I did it using an Item template setted up with Layout Element, nested in a Vertical Layout Group object.
The Item Template is composed by an image and a text description.
The script that i use to populate the list is an easy for:
for (int i = 0; i < details.Count && i < LenSize; i++){
Transform transf = Instantiate(template, container);
RectTransform trasnf2 = transf.GetComponent<RectTransform>();
height = -3 * i;
trasnf2.anchoredPosition = new Vector2(0, height);
transf.gameObject.SetActive(true);
transf.Find("Image").GetComponent<Text>().text = details[i].image;
transf.Find("Desc").GetComponent<Text>().text =
details[i].description;
}
All works properly when I run the app from my PC, I'm able to scroll the list by "left click + drag up/down" or mouse scroll wheel.
My problem appears when i build the app and run it from my android device, i not able to scroll the list with the touchscreen.
(Touchscreen inputs work fine, I have some button and they work)
Can someone help me with my problem?
I found that I can use Input manager to catch input event... but I not sure how use it to "scroll" on Android, because if i change offset of object with Vertical Layout Group, I move the entire object and I don't scroll the inside elements, else, if I change offset of child element (Item Templates) I change the single element position "breaking" the layout of the Vertical Layout Group;
I tried to find solutions, but I'm new and apparently nothing works when I apply it.
What am I doing wrong?
If your goal is to make a scrollable list of UI objects here is a gif I made on that process. It runs through the layout and each component and it working.
If you want to dynamically add objects to this list at runtime, use the Panel_Content transform and instantiate objects using Instantiate(yourPrefab, Pane_Content.transform, false). Let me know if you run into issues with this example.
I have implemented a horizontal recyclerView with LinearSnapHelper, to implement a UI input that selects a particular configuration. Kinda like the old school number picker/selector or spinner. The item in the center is the selected position.
it works fine and all, but here's the problem. On initial start up, I need to programmatically set the position of the recycler view such that the selected item (the index of which was loaded from disk) is position in the center.
.scrollToPosition() wont work becuase it places the selected item in the begining.
now I know I can do all the math and calculate the x coordinate and manually set it, but thats a lot of redundant work because LinearSnapHelper is already doing this, and I feel like there should be a way to just reuse that logic, but with actually initiating a fling.
I need something like LinearSnapHelper.snapToPosition()
More general solution:
First scroll RecyclerView to make target item visible.
Than, take the object of target View and use SnapHelper to determine
distance for the final snap.
Finally scroll to target position.
NOTE: This works only because programmatically you are scrolling at the exact position & covering the missing distance by exact value using scrollBy instead of doing smooth scrolling
Code snippet:
mRecyclerView.scrollToPosition(selectedPosition);
mRecyclerView.post(() -> {
View view = mLayoutManager.findViewByPosition(selectedPosition);
if (view == null) {
Log.e(WingPickerView.class.getSimpleName(), "Cant find target View for initial Snap");
return;
}
int[] snapDistance = mSnapHelper.calculateDistanceToFinalSnap(mLayoutManager, view);
if (snapDistance[0] != 0 || snapDistance[1] != 0) {
mRecyclerView.scrollBy(snapDistance[0], snapDistance[1]);
}
}
});
Try calling smoothScrollToPosition on the RecyclerView object, and passing the position index (int)
mRecyclerView.smoothScrollToPosition(position);
Worked for me with a LinearLayoutManager and LinearSnapHelper. It animates the initial scroll, but at least snaps the item in position.
This is my first post on the stack, hope it helps :)
I have a recyclerView which I have added padding at the left and right with dummy views in the adapter. So that the first "actual" item can be snapped to.
I couldn't get smoothScrollToPosition(0) to work though for the initial snap. I used the following
recycler.scrollBy(snapHelper.calculateDistanceToFinalSnap(binding.recycler.getLayoutManager(), recycler.getChildAt(1))[0], 0);
Isn't the nicest looking way, but seems to work!
I am trying to implement an ExpandableListView in Android that exhibits accordion type behavior (only one group can be open at a time). It is pretty simple to add code in the setOnGroupExpandListener to collapse one group when another is opened (see below).
getExpandableListView().setOnGroupExpandListener(new ExpandableListView.OnGroupExpandListener() {
#Override
public void onGroupExpand(int groupPosition) {
if (lastExpandedChapter != -1
&& groupPosition != lastExpandedChapter) {
getExpandableListView().collapseGroup(lastExpandedChapter);
}
lastExpandedChapter = groupPosition;
}
});
However, I've noticed that this seems to confuse the default scrolling logic in the ExpandableListView. When expanding a group, the default scroll will ensure the group's content is viewable (scrolling down as necessary to view it). This is essentially what I want.
When I add code to automatically collapse the previously opened group when a new one is expanded, the scroll gets a little wonky if the group being opened is below the one that is programmatically closed. The list view is scrolled too far up. It works fine if I click to close the groups, but when I do it programmatically in the handler, the scrolling is off.
Any suggestions how to either (A) change the automatic collapse code so it doesn't confuse the default scroll or (B) programmatically adjust the scroll of the list view to the correct location?
I've got a GridView with a custom Adapter, displaying thumbnails of pictures to the user. I can select and deselect pictures (I use setAlpha(0.25) on the views to notify the user of the change), and that's all well and fine.
Now what I want to do next, is have a button on top of the gridview that clears the whole selection, i.e. call setAlpha(1.0) on all views that were changed. So far I can reset my elements in the adapter, and I can setAlpha to 1, but the view doesn't update unless I scroll it out of the display, and then get back to it, or notify the adapter of changes, which redraws all my views, which does not look too pretty if only one element was selected.
I already dynamically set and reset individual elements through the GridView's onClickListener, but can't do this for more. I even tried calling performClick on all selected items through my button, but again it only displays the changes after the views have been out of the screen and are shown again.
Here's how I simulate the clicks:
for (int i = 0; i < mAdapter.getCount(); i++) {
PictureForSelection tempPic = (PictureForSelection) mAdapter.getItem(i);
if (tempPic.isPicSelected()) {
//tempPic.setIfSelected(false);
gridview.performItemClick(mAdapter.getView(i, null, null), i, i);
}
}
EDIT:
Conclusion - don't simulate clicks like this :)
So now I skip click simulation and use this:
gridview.getChildAt(i).setAlpha((float) 1.0);
In general it does exactly what I wanted it to do, except for one case:
If I have for example 3 pictures selected, one is off screen, one is partly shown, and one is fully shown, the ones shown (even partially) don't update their views.
If all the selected pictures are displayed it works flawlessly, but if some are out of the display, the rest do not update until the adapter's getView() gets called by Android. Still, thanks #mmlooloo for getting me so far.
If anyone knows a way around this, please do share :)
After setting your alpha you can call imageview.invalidate(); this causes your imageview to redraw itself.
if notifyDataSetChanged() is not working you can try like this.
i'm not sure whether this works,
1.onclick of the button you set the gridView.setAdapter(null);
2. Then set a new CustomAdapter object to gridview with default values.