I need to do something like this:
How should I do this?
Update
This is almost working.
GridLayoutManager layoutManager = new GridLayoutManager(this, 3);
layoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup()
{
#Override
public int getSpanSize(int position)
{
if (position == mElements.size()-1)
{
return 1;
}
else
{
SchedulePlanGlobal elem = mElements.get(position);
SchedulePlanGlobal elemNext = mElements.get(position+1);
if (elem.getRoomName().charAt(0) == elemNext.getRoomName().charAt(0))
{
return 1;
}
else
{
return calculateSpan(position);
}
}
}
private int calculateSpan(int position)
{
if (position == 0) return 3;
else
{
int currentPosition = position - 1;
int elements = 0;
while (currentPosition != 0)
{
if (mElements.get(currentPosition).getRoomName().charAt(0) == mElements.get(position).getRoomName().charAt(0))
{
elements ++;
}
else
{
break;
}
currentPosition --;
}
int spanPosition = (elements % 3) + 1;
switch (spanPosition)
{
case 1: return 3;
case 2: return 2;
default: return 1;
}
}
}
});
Implement and set GridLayoutManager.SpanSizeLookup.
override fun getSpanSize(position: Int) = /* set value based on position */
With that you can define how many spans (columns) item should use. In your case 4th item should take all 3 columns and other should take only 1 column.
A different layout manager might be an easier solution.
I suggest using https://github.com/google/flexbox-layout
You would set the layout manager to wrap, so if your items did not fit on the horizontal line then it would wrap it to a new line, this make it easy to fit on different screen sizes.
Then for your 5th item that you know you want on a new line you would use the layout_wrapBefore attribute. I would checkout the layout_wrapBefore example in the docs
Related
I have RecyclerView with 2 columns. What I want is to show last item in center if number of items in list is odd. Something like this
I have tried implementing all the so threads from here https://www.google.com/search?client=firefox-b-d&q=show+last+item+in+recyclerview+center+horizontal+android
I have used below code to set layout manager
gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
#Override
public int getSpanSize(int position) {
if (mLangInfos.size() % 2 == 0)
return 1;
else {
if (position == mLangInfos.size()-1)
return 2;
else
return 1;
}
}
});
recyclerView.setLayoutManager(gridLayoutManager);
But what this does is it shows last item in full width, see below image.
I know that I can implement a RecyclerView with different views for items on Android.
But my need is a little bit different here. I would like to know how can I implement a RecyclerView with a different number of items by rows.
For example, I would have 2 items for first row, then just 1 item for second row, then 3 items for third row, ...
Is it possible to implement this with a RecyclerView? If so, How can I implement it?
Thanks for your help.
Sylvain
What you're looking for can be done with GridLayoutManager itself, the only trick here is to use the least common multiple (lcm) of your column counts per row as your total span count. Here is an easy example:
#Override
public void onCreate() {
super.onCreate();
...
int spanCount = lcm(2, 1, 3, 8);
GridLayoutManager layoutManager = new GridLayoutManager(this, spanCount);
layoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
#Override
public int getSpanSize(int position) {
int numberOfColumns;
switch (position) {
case 0:
numberOfColumns = 2;
break;
case 1:
numberOfColumns = 1;
break;
case 2:
numberOfColumns = 3;
break;
case 3:
numberOfColumns = 8;
break;
default:
numberOfColumns = spanCount;
}
return spanCount / numberOfColumns;
}
});
recyclerView.setLayoutManager(layoutManager);
...
}
public static int lcm(int... input) {
int result = input[0];
for(int i = 1; i < input.length; i++) result = lcm(result, input[i]);
return result;
}
Yes it is possible, you just need to create different Views and get them in the
if (holder instanceof ProfileAdapter.VideoViewHolder) {
} else {
}
and throw the views inside the above method using
public int getItemViewType(int position) {
}
I want to implement a layout where I can drag an image and drop it to another screen similar to android launcher where we can place the app icons anywhere on a set of screens scrolling horizontally. I am not sure how to start and where to start. I am thinking of implementing a layout that would be larger than screen and then start autoscrolling as soon as the user touches the image.The position of dropping the image will be fixed. Any references or better approach to implement this?
I just wrote this: (its working for the time being)
Just addDragListener only on ViewPager. You don't need to addDragListener on gridviews.
Get Position of Drop on target Grid.
Keep track of source GridView and Target Gridview and use to get its adapter.
then Manipulate on Drop between adapter.
public void setDragListener() {
pager = getViewPager();
dragListener = new View.OnDragListener() {
GridViewAdapter sourceAdapter = null;
GridViewAdapter targetAdapter = null;
int currentOffset = 0;
#Override
public boolean onDrag(View v, DragEvent event) {
int currentX = -1;
int currentY = -1;
switch (event.getAction()) {
case DragEvent.ACTION_DRAG_STARTED:
if (!pager.isFakeDragging()) {
pager.beginFakeDrag();
}
break;
case DragEvent.ACTION_DRAG_ENTERED:
pager.setCurrentItem(pager.getCurrentItem());
currentOffset = (int) (event.getX());
if (!pager.isFakeDragging()) {
pager.beginFakeDrag();
}
break;
case DragEvent.ACTION_DRAG_EXITED:
break;
case DragEvent.ACTION_DRAG_LOCATION:
if (pager != null) {
if (!pager.isFakeDragging()) {
pager.beginFakeDrag();
}
if (pager.isFakeDragging()) {
int maxWidth = pager.getWidth() * pager.getCount();
int offset = (int) (event.getX());
int scrollX = getScrollX();
int dragBy = (int) -1 * (offset - currentOffset);
currentOffset = offset;
pager.fakeDragBy(dragBy * pager.getCount());
int scrolledPage = (int) (offset + scrollX) / pager.getWidth();
if (pager.getCurrentItem() != scrolledPage) {
pager.setCurrentItem(Math.abs(scrolledPage));
}
}
}
break;
case DragEvent.ACTION_DRAG_ENDED:
if (pager.isFakeDragging()) {
pager.endFakeDrag();
}
break;
case DragEvent.ACTION_DROP:
if (pager.isFakeDragging()) {
pager.endFakeDrag();
}
currentX = (int) event.getX();
currentY = (int) event.getY();
// u can use meta data and other info which u passed when longPress
// u can set sourcePage and sourcePosition in meta data.
metaMove = event.getLocalState();
sourceAdapter = // get Source Gridviews adapter
targetAdapter = // get Target GridViews Adapter ie current Page's gridviews adapter.
GridView g = // get The gridview current Page gridview
if (g != null) {
int position = g.pointToPosition(currentX + (getScrollX() - (pager.getWidth() * pager.getCurrentItem())), currentY);
metaMove.targetPosition = position;
metaMove.targetPage = currentPage;
getActivity().runOnUiThread(new Runnable() {
#Override
public void run() {
// u can move from source to target by inset and remove.
// use some broadcast function to reset if page does not have any item left while moving. Ur logic here.
move(sourceAdapter, targetAdapter, metaMove);
}
});
}
break;
default:
break;
}
return true;
}
};
pager.setOnDragListener(dragListener);
}
public int getScrollX() {
int maxWidth = pager.getWidth() * pager.getCount();
int scrollX = pager.getScrollX();
if (pager.getScrollX() < 0) {
scrollX = maxWidth + scrollX - pager.getWidth();
} else if (pager.getScrollX() == 0) {
if (pager.getCurrentItem() == (pager.getCount() - 1)) {
scrollX = maxWidth + scrollX - pager.getWidth();
}
}
return scrollX;
}
Use following onLongPress on your sourceGrid:
metaMove.sourcePage = // sourcePage;
metaMove.sourcePosition = position;
metaMove.targetPosition = -1; // to be set on drop
metaMove.targetPage = -1 // to be set on drop;
boolean dragStarted = view.startDrag(null,
myShadow,
metaMove,
0
);
Use ViewPager for multiple pages.
Use GridView (in fragment) on ViewPager on each viewpager page.
setOnDragListener on viewPager only once // important
setOnDragListener on each GridView on pages// important
get position = pointToPosition(event.getX(),event.getY()) on target GridView on ACTION_DROP (add here ur icon or what ever u r moving).
ACTION_DRAG_END to use for removing from the source GridView.
use ACTION_DRAG_EXIT from gridView to change viewPager page to & fro.
Note : I assume you are using gridView.setOnItemLongClickListener() for startDrag() etc. Refer it on android site.
Does smoothScrollToPosition() method for GridView works correctly?
I have found an open bug report and mine is not working correctly either.
setSelection() is working fine but I want a smooth scroll effect.
Do you have any idea about this issue?
If this is a persistent problem, where should I start to implement a good scroll effect?
While I'm not seeing any issues running the example code you posted, if you're not seeing consistent results in your application it's not too tricky to create your own scroll controller. Here's an example implementation you could use:
private class ScrollPositioner {
private static final int SCROLL_DURATION = 20;
private static final int DIR_UP = 1;
private static final int DIR_DOWN = 2;
int mTargetPosition = AdapterView.INVALID_POSITION;
int mDirection = AdapterView.INVALID_POSITION;
int mLastSeenPosition = AdapterView.INVALID_POSITION;
int mExtraScroll;
GridView mGrid;
public ScrollPositioner(GridView grid) {
mGrid = grid;
mExtraScroll = ViewConfiguration.get(mGrid.getContext()).getScaledFadingEdgeLength();
}
Handler mHandler = new Handler();
Runnable mScroller = new Runnable() {
public void run() {
int firstPos = mGrid.getFirstVisiblePosition();
switch(mDirection) {
case DIR_UP: {
if (firstPos == mLastSeenPosition) {
// No new views, let things keep going.
mHandler.postDelayed(mScroller, SCROLL_DURATION);
return;
}
final View firstView = mGrid.getChildAt(0);
if (firstView == null) {
return;
}
final int firstViewTop = firstView.getTop();
final int extraScroll = firstPos > 0 ? mExtraScroll : mGrid.getPaddingTop();
mGrid.smoothScrollBy(firstViewTop - extraScroll, SCROLL_DURATION);
mLastSeenPosition = firstPos;
if (firstPos > mTargetPosition) {
mHandler.postDelayed(mScroller, SCROLL_DURATION);
}
break;
}
case DIR_DOWN: {
final int lastViewIndex = mGrid.getChildCount() - 1;
final int lastPos = firstPos + lastViewIndex;
if (lastViewIndex < 0) {
return;
}
if (lastPos == mLastSeenPosition) {
// No new views, let things keep going.
mHandler.postDelayed(mScroller, SCROLL_DURATION);
return;
}
final View lastView = mGrid.getChildAt(lastViewIndex);
final int lastViewHeight = lastView.getHeight();
final int lastViewTop = lastView.getTop();
final int lastViewPixelsShowing = mGrid.getHeight() - lastViewTop;
final int extraScroll = lastPos < mGrid.getAdapter().getCount() - 1 ? mExtraScroll : mGrid.getPaddingBottom();
mGrid.smoothScrollBy(lastViewHeight - lastViewPixelsShowing + extraScroll, SCROLL_DURATION);
mLastSeenPosition = lastPos;
if (lastPos < mTargetPosition) {
mHandler.postDelayed(mScroller, SCROLL_DURATION);
}
break;
}
default:
break;
}
}
};
public void scrollToPosition(int position) {
mTargetPosition = position;
mLastSeenPosition = AdapterView.INVALID_POSITION;
if(position < mGrid.getFirstVisiblePosition()) {
mDirection = DIR_UP;
} else if (position > mGrid.getLastVisiblePosition()) {
mDirection = DIR_DOWN;
} else {
return;
}
mHandler.post(mScroller);
}
}
Here's a snippet from the example you linked that includes where this new class to do the scrolling in place of the framework implementation:
GridView g;
boolean t= false;
#Override
public void onBackPressed() {
//Instantiate and attach the custom positioner
if(mPositioner == null) {
mPositioner = new ScrollPositioner(g);
}
//Use the custom object to scroll the view
mPositioner.scrollToPosition(0);
if(t) {
super.onBackPressed();
}
else {
t = true;
}
}
If you want to add a feature where the selected position is always scrolled to the top even when the scrolling direction is down, you could do that. This is not something the framework's implementation is designed to do, but you could accomplish it by adding some code in DIR_DOWN to continue scrolling until the first visible position matches target (like DIR_UP does). You must also beware of the case where the scrolling ends before the position reaches the top, so you aren't constantly posting the handler in cases where you will never get a different result.
HTH
Had the same issue- I built a simple GridView and for some reason the smoothScrollToPosition() didn't work at all (just bounce from time to time).
after lot of debugging it turn out to be that I need to remove the android:padding parameter from the GridView.
very strange, I really don't know why it is like this, but at least now it works.
Hi I'm using the TouchListView control from here: https://github.com/commonsguy/cwac-touchlist
and I've added some buttons to add to the list in the footer:
mFooter = getLayoutInflater().inflate(R.layout.edit_homepage_footer_layout, null);
mListView = (TouchListView) findViewById(R.id.sectionList);
mListView.addFooterView(mFooter);
It all seems to be working fine until I drag an item in the list, at which point the footer collapses (to the height of one list item I think) obscuring the buttons I have added.
Can anyone suggest a fix/workaround for this?
I actually worked this out shortly after asking it (always the way...)
The issue is in the doExpansion() and unExpandViews() methods which were modifying every item in the list including the footer. To fix it I created a method to check whether we are dealing with a draggable item or the footer:
private boolean isDraggableItem(View view) {
View dragger = view.findViewById(grabberId);
return dragger != null;
}
And then modified the methods mentioned as follows:
private void unExpandViews(boolean deletion) {
for (int i = 0; ; i++) {
View v = getChildAt(i);
if (v == null) {
if (deletion) {
// HACK force update of mItemCount
int position = getFirstVisiblePosition();
int y = getChildAt(0).getTop();
setAdapter(getAdapter());
setSelectionFromTop(position, y);
// end hack
}
layoutChildren(); // force children to be recreated where needed
v = getChildAt(i);
if (v == null) {
break;
}
}
if (isDraggableItem(v)) { //check this view isn't the footer
ViewGroup.LayoutParams params = v.getLayoutParams();
params.height = mItemHeightNormal;
v.setLayoutParams(params);
v.setVisibility(View.VISIBLE);
}
}
}
private void doExpansion() {
Log.d(logTag, "Doing expansion");
int childnum = mDragPos - getFirstVisiblePosition();
if (mDragPos > mFirstDragPos) {
childnum++;
}
View first = getChildAt(mFirstDragPos - getFirstVisiblePosition());
for (int i = 0; ; i++) {
View vv = getChildAt(i);
if (vv == null) {
break;
}
int height = mItemHeightNormal;
int visibility = View.VISIBLE;
if (vv.equals(first)) {
// processing the item that is being dragged
if (mDragPos == mFirstDragPos) {
// hovering over the original location
visibility = View.INVISIBLE;
} else {
// not hovering over it
height = 1;
}
} else if (i == childnum) {
if (mDragPos < getCount() - 1) {
height = mItemHeightExpanded;
}
}
if (isDraggableItem(vv)) { //check this view isn't the footer
ViewGroup.LayoutParams params = vv.getLayoutParams();
params.height = height;
vv.setLayoutParams(params);
vv.setVisibility(visibility);
}
}
}
Would be worth updating the github project to include this I think.