Smooth Scroll for GridView - android

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.

Related

Implementing auto fling at an interval on a Recycler View in android

I have a recycler view with the following attributes in the xml file.
NOTE : I AM DISPLAYING ONLY ONE ITEM OF AT A TIME ON THE SCREEN FOR THIS RECYCLER VIEW.
<MyCustomRecyclerView
android:id="#+id/my_rv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipToPadding="false"
android:nestedScrollingEnabled="false"
android:orientation="horizontal"
android:overScrollMode="never"
android:paddingHorizontal="4dp"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"/>
And I am using a PagerSnapHelper to move to the position left or right based on the center of the view on the screen.
val snapHelper = PagerSnapHelper()
snapHelper.attachToRecyclerView(this)
It's working fine for a manual scroll action performed.
Now, I want to add an auto scroll as well after a certain interval of time (say 2.5 seconds). I have created a handler and posted a runnable on it with a delay of 2.5 seconds. I am trying to call fling(velocityX, velocityY) of the RecyclerView with a good enough value of velocityX
val scrollHandler = Handler()
val SCROLL_INTERVAL:Long = 2500 //scroll period in ms
val runnable = object : Runnable {
override fun run() {
//velocityX = 7500
fling(7500, 0)
scrollHandler.postDelayed(this, SCROLL_INTERVAL.toLong())
}
}
But the PagerSnaperHelper::findTargetSnapPosition() not returning correct target position because the View actually has not changed on the screen as in case of a manual scroll. It is returning the position of the element which is already visible on the screen.
#Override
public int findTargetSnapPosition(RecyclerView.LayoutManager layoutManager, int velocityX,
int velocityY) {
final int itemCount = layoutManager.getItemCount();
if (itemCount == 0) {
return RecyclerView.NO_POSITION;
}
final OrientationHelper orientationHelper = getOrientationHelper(layoutManager);
if (orientationHelper == null) {
return RecyclerView.NO_POSITION;
}
// A child that is exactly in the center is eligible for both before and after
View closestChildBeforeCenter = null;
int distanceBefore = Integer.MIN_VALUE;
View closestChildAfterCenter = null;
int distanceAfter = Integer.MAX_VALUE;
// Find the first view before the center, and the first view after the center
final int childCount = layoutManager.getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = layoutManager.getChildAt(i);
if (child == null) {
continue;
}
final int distance = distanceToCenter(layoutManager, child, orientationHelper);
if (distance <= 0 && distance > distanceBefore) {
// Child is before the center and closer then the previous best
distanceBefore = distance;
closestChildBeforeCenter = child;
}
if (distance >= 0 && distance < distanceAfter) {
// Child is after the center and closer then the previous best
distanceAfter = distance;
closestChildAfterCenter = child;
}
}
// Return the position of the first child from the center, in the direction of the fling
final boolean forwardDirection = isForwardFling(layoutManager, velocityX, velocityY);
if (forwardDirection && closestChildAfterCenter != null) {
return layoutManager.getPosition(closestChildAfterCenter);
} else if (!forwardDirection && closestChildBeforeCenter != null) {
return layoutManager.getPosition(closestChildBeforeCenter);
}
// There is no child in the direction of the fling. Either it doesn't exist (start/end of
// the list), or it is not yet attached (very rare case when children are larger then the
// viewport). Extrapolate from the child that is visible to get the position of the view to
// snap to.
View visibleView = forwardDirection ? closestChildBeforeCenter : closestChildAfterCenter;
if (visibleView == null) {
return RecyclerView.NO_POSITION;
}
int visiblePosition = layoutManager.getPosition(visibleView);
int snapToPosition = visiblePosition
+ (isReverseLayout(layoutManager) == forwardDirection ? -1 : +1);
if (snapToPosition < 0 || snapToPosition >= itemCount) {
return RecyclerView.NO_POSITION;
}
return snapToPosition;
}
I would like to know how can I achieve the desired result?
I got a workaround to solve this. Before calling fling(), I called scrollBy(x,y) to scroll the items as if it would have happened during a manual scroll.
val runnable = object : Runnable {
override fun run() {
scrollBy(400,0)
//velocityX = 7500
fling(7500, 0)
scrollHandler.postDelayed(this, SCROLL_INTERVAL.toLong())
}
}

GridLayoutManager recalculate number of columns

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

ListView item scroll animation ("UIKit Dynamics" -like)

I am attempting to animate the ListView items when a scroll takes place. More specifically, I am trying to emulate the scroll animations from the iMessage app on iOS 7. I found a similar example online:
To clarify, I'm trying to achieve the "fluid" movement effect on the items when the user scrolls, not the animation when a new item is added. I've attempted to modify the Views in my BaseAdapter and I've looked into the AbsListView source to see if I could somehow attach an AccelerateInterpolator somewhere that would adjust the draw coordinates sent to the children Views (if that is even how AbsListView is designed). I've been unable to make any progress so far.
Does anybody have any ideas of how to replicate this behaviour?
For the record to help with googling: this is called "UIKit Dynamics" on ios.
How to replicate Messages bouncing bubbles in iOS 7
It is built-in to recent iOS releases. However it's still somewhat hard to use. (2014) This is the post on it everyone copies:widely copied article Surprisingly, UIKit Dynamics is only available on apple's "collection view", not on apple's "table view" so all the iOS debs are having to convert stuff from table view to "collection view"
The library everyone is using as a starting point is BPXLFlowLayout, since that person pretty much cracked copying the feel of the iphone text messages app. In fact, if you were porting it to Android I guess you could use the parameters in there to get the same feel. FYI I noticed in my android fone collection, HTC phones have this effect, on their UI. Hope it helps. Android rocks!
This implementation works quite good. There is some flickering though, probably because of altered indices when the adapter add new views to top or bottom..That could be possibly solved by watching for changes in the tree and shifting the indices on the fly..
public class ElasticListView extends GridView implements AbsListView.OnScrollListener, View.OnTouchListener {
private static int SCROLLING_UP = 1;
private static int SCROLLING_DOWN = 2;
private int mScrollState;
private int mScrollDirection;
private int mTouchedIndex;
private View mTouchedView;
private int mScrollOffset;
private int mStartScrollOffset;
private boolean mAnimate;
private HashMap<View, ViewPropertyAnimator> animatedItems;
public ElasticListView(Context context) {
super(context);
init();
}
public ElasticListView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public ElasticListView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
private void init() {
mScrollState = SCROLL_STATE_IDLE;
mScrollDirection = 0;
mStartScrollOffset = -1;
mTouchedIndex = Integer.MAX_VALUE;
mAnimate = true;
animatedItems = new HashMap<>();
this.setOnTouchListener(this);
this.setOnScrollListener(this);
}
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
if (mScrollState != scrollState) {
mScrollState = scrollState;
mAnimate = true;
}
if (scrollState == SCROLL_STATE_IDLE) {
mStartScrollOffset = Integer.MAX_VALUE;
mAnimate = true;
startAnimations();
}
}
#Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
if (mScrollState == SCROLL_STATE_TOUCH_SCROLL) {
if (mStartScrollOffset == Integer.MAX_VALUE) {
mTouchedView = getChildAt(mTouchedIndex - getPositionForView(getChildAt(0)));
if (mTouchedView == null) return;
mStartScrollOffset = mTouchedView.getTop();
} else if (mTouchedView == null) return;
mScrollOffset = mTouchedView.getTop() - mStartScrollOffset;
int tmpScrollDirection;
if (mScrollOffset > 0) {
tmpScrollDirection = SCROLLING_UP;
} else {
tmpScrollDirection = SCROLLING_DOWN;
}
if (mScrollDirection != tmpScrollDirection) {
startAnimations();
mScrollDirection = tmpScrollDirection;
}
if (Math.abs(mScrollOffset) > 200) {
mAnimate = false;
startAnimations();
}
Log.d("test", "direction:" + (mScrollDirection == SCROLLING_UP ? "up" : "down") + ", scrollOffset:" + mScrollOffset + ", toucheId:" + mTouchedIndex + ", fvisible:" + firstVisibleItem + ", " +
"visibleItemCount:" + visibleItemCount + ", " +
"totalCount:" + totalItemCount);
int indexOfLastAnimatedItem = mScrollDirection == SCROLLING_DOWN ?
getPositionForView(getChildAt(0)) + getChildCount() :
getPositionForView(getChildAt(0));
//check for bounds
if (indexOfLastAnimatedItem >= getChildCount()) {
indexOfLastAnimatedItem = getChildCount() - 1;
} else if (indexOfLastAnimatedItem < 0) {
indexOfLastAnimatedItem = 0;
}
if (mScrollDirection == SCROLLING_DOWN) {
setAnimationForScrollingDown(mTouchedIndex - getPositionForView(getChildAt(0)), indexOfLastAnimatedItem, firstVisibleItem);
} else {
setAnimationForScrollingUp(mTouchedIndex - getPositionForView(getChildAt(0)), indexOfLastAnimatedItem, firstVisibleItem);
}
if (Math.abs(mScrollOffset) > 200) {
mAnimate = false;
startAnimations();
mTouchedView = null;
mScrollDirection = 0;
mStartScrollOffset = -1;
mTouchedIndex = Integer.MAX_VALUE;
mAnimate = true;
}
}
}
private void startAnimations() {
for (ViewPropertyAnimator animator : animatedItems.values()) {
animator.start();
}
animatedItems.clear();
}
private void setAnimationForScrollingDown(int indexOfTouchedChild, int indexOflastAnimatedChild, int firstVisibleIndex) {
for (int i = indexOfTouchedChild + 1; i <= indexOflastAnimatedChild; i++) {
View v = getChildAt(i);
v.setTranslationY((-1f * mScrollOffset));
if (!animatedItems.containsKey(v)) {
animatedItems.put(v, v.animate().translationY(0).setDuration(300).setStartDelay(50 * i));
}
}
}
private void setAnimationForScrollingUp(int indexOfTouchedChild, int indexOflastAnimatedChild, int firstVisibleIndex) {
for (int i = indexOfTouchedChild - 1; i > 0; i--) {
View v = getChildAt(i);
v.setTranslationY((-1 * mScrollOffset));
if (!animatedItems.containsKey(v)) {
animatedItems.put(v, v.animate().translationY(0).setDuration(300).setStartDelay(50 * (indexOfTouchedChild - i)));
}
}
}
#Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
Rect rect = new Rect();
int childCount = getChildCount();
int[] listViewCoords = new int[2];
getLocationOnScreen(listViewCoords);
int x = (int)event.getRawX() - listViewCoords[0];
int y = (int)event.getRawY() - listViewCoords[1];
View child;
for (int i = 0; i < childCount; i++) {
child = getChildAt(i);
child.getHitRect(rect);
if (rect.contains(x, y)) {
mTouchedIndex = getPositionForView(child);
break;
}
}
return false;
}
return false;
}
}
I've taken just a few minutes to explore this and it looks like it can be done pretty easily with API 12 and above (hopefully I'm not missing something ...). To get the very basic card effect, all it takes is a couple lines of code at the end of getView() in your Adapter right before you return it to the list. Here's the entire Adapter:
public class MyAdapter extends ArrayAdapter<String>{
private int mLastPosition;
public MyAdapter(Context context, ArrayList<String> objects) {
super(context, 0, objects);
}
private class ViewHolder{
public TextView mTextView;
}
#TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
#Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView == null) {
holder = new ViewHolder();
convertView = LayoutInflater.from(getContext()).inflate(R.layout.grid_item, parent, false);
holder.mTextView = (TextView) convertView.findViewById(R.id.checkbox);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
holder.mTextView.setText(getItem(position));
// This tells the view where to start based on the direction of the scroll.
// If the last position to be loaded is <= the current position, we want
// the views to start below their ending point (500f further down).
// Otherwise, we start above the ending point.
float initialTranslation = (mLastPosition <= position ? 500f : -500f);
convertView.setTranslationY(initialTranslation);
convertView.animate()
.setInterpolator(new DecelerateInterpolator(1.0f))
.translationY(0f)
.setDuration(300l)
.setListener(null);
// Keep track of the last position we loaded
mLastPosition = position;
return convertView;
}
}
Note that I'm keeping track of the last position to be loaded (mLastPosition) in order to determine whether to animate the views up from the bottom (if scrolling down) or down from the top (if we're scrolling up).
The wonderful thing is, you can do so much more by just modifying the initial convertView properties (e.g. convertView.setScaleX(float scale)) and the convertView.animate() chain (e.g. .scaleX(float)).
Try this by putting this in your getView() method Just before returning your convertView:
Animation animationY = new TranslateAnimation(0, 0, holder.llParent.getHeight()/4, 0);
animationY.setDuration(1000);
Yourconvertview.startAnimation(animationY);
animationY = null;
Where llParent = RootLayout which consists your Custom Row Item.
It's honestly going to be a lot of work and quite mathematically intense, but I would have thought you could make the list item's layouts have padding top and bottom and that you could adjust that padding for each item so that the individual items become more or less spaced out. How you would track by how much and how you would know the speed at which the items are being scrolled, well that would be the hard part.
Since we do want items to pop every time they appear at the top or bottom of our list, the best place to do it is the getView() method of the adapter:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
animatePostHc(position, v);
} else {
animatePreHc(position, v);
}
From what I understand what you are looking for is a parallax effect.
This answer is really complete and I think that can help you a lot.
Use this library: http://nhaarman.github.io/ListViewAnimations
It is very awesome. Better than the iOS in atleast it is open source :)

Android Gallery freezes screen until scrolled

I have a full-screen custom Gallery. Elements of the gallery have buttons and other interactive areas. Some user interface is done through dialog boxes that pop in front of the gallery; when a dialog box is dismissed, the user is taken back to the gallery.
Most of the time, this works fine. However, sometimes, after a dialog box is dismissed, the buttons stop taking user input. The gallery, on the other hand, still scrolls. What's even more bizarre, as soon as I scroll the gallery, the system processes those clicks I thought failed (pops up a dialog, etc.).
It is easy to say that the main UI thread is locked. Why is it locked? How do I unlock it? Any help will be appreciated. Below is the full code of the class.
UPDATE. One of the elements within the Gallery is a HorizontalScrollView. When I try scroll it, mouse messages come through; I stepped through them and saw that the scrollBy() and invalidate() are properly called. Then I printed out the message queue. The only event that passes through the main loop is 1006, which I assume is the Touch event. The Draw event, the 1000, never makes it. Once I scroll the Gallery back and forth -- lo and behold -- the message queue starts receiving the 1000's, so the HorizontalScrollView scrolls fine!
So the question becomes: what stops the Draw events, and how do I make sure they are sent to the queue?
public class PlayerGallery extends Gallery
{
// 4 buttons to display
private final static int BUT_BIRD = 0;
private final static int BUT_SCORE = 1;
private final static int BUT_ROUND = 2;
private final static int BUT_MOVE = 3;
private final static int N_BUTTONS = 4;
// button images
private final static Drawable[] imgButtons =
{
WonDraw.WW.bird,
WonDraw.WW.scores,
WonDraw.WW.moveSumm,
WonDraw.WW.lastMove,
};
// individual player views
private PlayerView[] views;
// card that was clicked in the current player view
private Card clickedCard = null;
// wonder that was clicked in the current player view
private Player clickedWonderPlayer = null;
// one player
private final class PlayerView extends RelativeLayout
implements OnClickListener
{
// base view ID for the buttons
private final static int BUT_VIEW_ID = 200;
// player to display
final Player player;
// drawing data
private PlayerDisplay pd = null;
// the sub-view on top that shows player's hand
private HandView handView = null;
// button that takes user into bird's view
private final GlassButton[] buttons = new GlassButton[N_BUTTONS];
public PlayerView(Player player)
{
super(WonActivity.W.getApplicationContext());
setBackgroundColor(Color.TRANSPARENT);
this.player = player;
RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(
LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT);
lp.addRule(ALIGN_PARENT_TOP);
handView = new HandView();
addView(handView, lp);
for (int i = 0; i < buttons.length; ++i)
{
lp = new RelativeLayout.LayoutParams(
LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
if (i % 2 == 0)
lp.addRule(ALIGN_PARENT_RIGHT);
else
lp.addRule(LEFT_OF, buttons[i - 1].getId());
lp.addRule(BELOW, i / 2 == 0 ? handView.getId() : buttons[i - 2].getId());
GlassButton but = new ImgGlassButton(GlassButton.ROUND, imgButtons[i]);
but.setId(BUT_VIEW_ID + i);
but.setOnClickListener(this);
buttons[i] = but; addView(but, lp);
}
}
// reset for the next player
void reset(boolean useBigCards)
{
pd = WonActivity.W.getDisplay(player.id, useBigCards);
if (useBigCards)
{
handView.pd = pd;
handView.hand = WonActivity.W.getCurrentHand();
handView.setVisibility(VISIBLE);
handView.requestLayout();
handView.scrollTo(0, 0);
}
else
{
handView.pd = null;
handView.hand = null;
handView.setVisibility(GONE);
}
for (int i = BUT_ROUND; i <= BUT_MOVE; ++i)
buttons[i].setEnabled(Table.T.movesAvailable());
invalidate();
}
#Override
protected void dispatchSetPressed(boolean pressed)
{
// if I don't do that, bird button gets pressed when I scroll the gallery!
}
#Override
public boolean onTouchEvent(MotionEvent event)
{
if (event.getAction() == MotionEvent.ACTION_DOWN)
{
int x = (int)event.getX();
int y = (int)event.getY();
clickedCard = pd.findSmallCard(x, y);
clickedWonderPlayer = clickedCard == null && pd.isInWonder(x, y) ?
player : null;
}
return super.onTouchEvent(event);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
super.onMeasure(MeasureSpec.makeMeasureSpec(WonActivity.W.getWidth() - 2, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(WonActivity.W.getHeight(), MeasureSpec.EXACTLY));
setBackgroundColor(Color.TRANSPARENT);
setBackgroundDrawable(WonDraw.W.getWood());
}
#Override
protected void onDraw(Canvas canvas)
{
super.onDraw(canvas);
if (pd == null) return;
canvas.save();
pd.draw(canvas, handView.hand, true);
canvas.restore();
}
public void onClick(View v)
{
switch (v.getId() - BUT_VIEW_ID)
{
case BUT_BIRD:
WonActivity.W.switchToBird(player.id);
break;
case BUT_SCORE:
WonActivity.W.showScoreDlg();
break;
case BUT_ROUND:
WonActivity.W.showRoundDlg();
break;
case BUT_MOVE:
break;
}
}
};
// custom adapter for the gallery: provides circular functionality
private class PGAdapter extends BaseAdapter
{
public int getCount()
{
return Integer.MAX_VALUE;
}
public Object getItem(int position)
{
return position;
}
public long getItemId(int position)
{
return position;
}
public View getView(int position, View convertView, ViewGroup parent)
{
return views[position % views.length];
}
};
public PlayerGallery()
{
super(WonActivity.W.getApplicationContext());
setSpacing(0);
setBackgroundColor(Color.TRANSPARENT);
views = new PlayerView[Table.T.getPlayerCount()];
for (int i = 0; i < Table.T.getPlayerCount(); ++i)
views[i] = new PlayerView(Table.T.getPlayer(i));
setAdapter(new PGAdapter());
setHorizontalFadingEdgeEnabled(false);
setVerticalFadingEdgeEnabled(false);
}
// reset for the next player
void changeMovingPlayer()
{
for (int i = 0; i < views.length; ++i)
views[i].reset(i == WonActivity.W.getCurrentPlayerID());
setViewedPlayer(Math.max(WonActivity.W.getCurrentPlayerID(), 0));
}
// set the player whose buildings to view
void setViewedPlayer(int index)
{
int pos = Integer.MAX_VALUE / 2;
pos -= pos % views.length;
setSelection(pos + index);
views[index].requestFocus();
views[index].invalidate();
}
#Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY)
{
int kEvent = e2.getX() > e1.getX() ?
KeyEvent.KEYCODE_DPAD_LEFT :
KeyEvent.KEYCODE_DPAD_RIGHT;
onKeyDown(kEvent, null);
return true;
}
#Override
public boolean onTouchEvent(MotionEvent event)
{
boolean b = super.onTouchEvent(event);
switch (event.getAction())
{
case MotionEvent.ACTION_DOWN:
return true;
case MotionEvent.ACTION_UP:
if (event.getEventTime() - event.getDownTime() < WonActivity.CLICK_MS)
{
if (clickedCard != null)
WonActivity.W.showCardDlg(clickedCard);
else if (clickedWonderPlayer != null)
WonActivity.W.showWonderDlg(clickedWonderPlayer);
}
clickedCard = null;
clickedWonderPlayer = null;
break;
}
return b;
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
int w = MeasureSpec.getSize(widthMeasureSpec);
int h = MeasureSpec.getSize(heightMeasureSpec);
WonActivity.W.resize(w, h);
super.onMeasure(MeasureSpec.makeMeasureSpec(w, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(h, MeasureSpec.EXACTLY));
}
}

Add footer to Android TouchListView

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.

Categories

Resources