Unity: Vertical Group Layout Scroll doesn't work with Android - android

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.

Related

How to programmatically snap to position on Recycler view with LinearSnapHelper

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!

Android - dynamically update GridView views

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.

Code to determine when to place arrows for a horizontalscrollview

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);

FastScroller only scrolls back to the first element, not to the header view

I have enabled fast scrolling in a ListView, which has a not selectable headerview. If you scroll down the list and drag the fast scroll thumb to the top, the list only scrolls back to the first element, but not to the header view. Dragging the list, works as expected.
Screenshot1: The red area in the screenshot is the header view.
Screenshot2: If you drag the thumb to the top you get only to the first element and the header view is still above.
ListView lv = (ListView) findViewById(R.id.listView);
lv.addHeaderView(getLayoutInflater().inflate(R.layout.view,null), null, false);
<ListView
android:layout_height="fill_parent"
android:id="#+id/listView"
android:layout_width="fill_parent"
android:fastScrollEnabled="true"
></ListView>
I have created a demo project:
https://github.com/mikegr/fastscroll-bug
Why does dragging the thumb not scroll back to the top?
This is deliberate behavior of the FastScroller. When you call setAdapter on your ListView, the adapter is wrapped in a HeaderViewListAdapter if there are any headers set; this is why you must call addHeaderView before setAdapter. Then, in the FastScroller code, we see:
if (adapter instanceof HeaderViewListAdapter) {
mListOffset = ((HeaderViewListAdapter)adapter).getHeadersCount();
adapter = ((HeaderViewListAdapter)adapter).getWrappedAdapter();
}
That is, get an offset and use the underlying adapter. mListOffset is then used to set the top position to scroll to with the fast scroller. So where does this wrapping actually happen? Up to, as expected, ListView.addHeaderView, where we see:
if (mHeaderViewInfos.size() > 0|| mFooterViewInfos.size() > 0) {
mAdapter = new HeaderViewListAdapter(mHeaderViewInfos, mFooterViewInfos, adapter);
} else {
mAdapter = adapter;
}
So we're definitely looking around the right place. Now, it sounds like your goal is to NOT have the offset behavior for list headers for your fast thumb, but otherwise have a normal list with header. To do this, it's sufficient (based on what we've seen of the code) to have FastScroller.mListOffset = 0. This is set only in getSectionsFromIndexer, which is called unconditionally in init, and conditionally in several other functions only when mListAdapter == null. mListAdapter is only null if onSectionsChanged gets called, so let's ignore that path for now.
After a lot of digging around, and playing with various reflection hooks, I can say that there's no way to do this that will be even slightly future-compatible. You can use reflection to swap out the HeaderViewListAdapter for one that lies about its header count, etc; but that's quite fragile. Similarly, you can subclass the (package visible) FastScroller with one with your own behavior; but mListOffset is referenced widely and not through a getter, so this is even uglier than usual. Basically, you're running up against the fact that the system doesn't work quite the way you want.
I hesitate to call this a bug, since it's so clear from the code that it's deliberate behavior. Have you considered making the first element of the list just a special first element (possibly using a custom WrapperListAdapter much like HeaderViewListAdapter if desired for book-keeping), rather than using the header mechanism?

How to get ListView to grow to the size of content

So I have a fairly complex activity the parent being a linearlayout with a table with some basic info and buttons. Then a couple listviews that the user can add items to. So these listviews grow more and more as the user uses the app. The problem I'm running into is the Linearlayout is bigger then the resolution of the screen and so it needs to scroll. So the scrolling doesn't work on the Listviews. I've tried playing with changing the layout_height of the listview and its child element with no success. Is there a way to make these couple listviews expand out to the amount of children? Or am I going about this all wrong? If so what other controls can I use?
I'm open to any ideas. I really just don't know how to go about this.
Thanks
The problem I'm running into is the
Linearlayout is bigger then the
resolution of the screen and so it
needs to scroll. So the scrolling
doesn't work on the Listviews.
You need to rethink the way you are building your UI, as you cannot have ListViews inside of a ScrollView.
One possibility is to put everything in the ListView, marking some rows as being non-clickable, so ListView handles your scrolling. You can use my MergeAdapter for cases like this.
If you have a simple layout then you can replace your ListView component with a LinearLayout and manually program together the functionality you need to add/remove and click the items within it.
Here is a simple example of my own. refreshStudentList() has taken the place of notifyDataSetChanged() being called on the adapter, recentConnections is a LinearLayout and adapter is an instance of an Adapter (You can use the adapter you were using for your ListView):
private void refreshStudentList() {
recentConnections.removeAllViews();
for(int i=0; i < adapter.getCount(); i++) {
View toAdd = adapter.getView(i, null, recentConnections);
final int position = i;
toAdd.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
// onItemClick functionality here
}
});
recentConnections.addView(toAdd, new LayoutParams(LayoutParams.MATCH_PARENT,
(int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 50, getResources().getDisplayMetrics())));
}
recentConnections.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
}
As you can see, instead of an onItemClickListener I've set a onClick listener to each list item. This could easily be changed to be an onTouch listener if you wanted to implement the look and feel of clicking on a list item (by changing the view's background on MotionEvent.ACTION_DOWN and MotionEvent.ACTION_UP).
The other thing you need to note is I have hard coded a dp value for the height of each value (50dp in this case). When you are creating and inflating views programatically they don't have a "height" value so "wrap_content" becomes 0.
also, notifyDataSetChanged() on your adapter will do nothing.
As I said, this is a simple solution but it's the start of building up your own ExtendingListView custom component if that's what you're eventually aiming for.

Categories

Resources