I am using a custom list view, called headerListView and I am trying to set the selector/ tap down color. Now on a regular list view I do it like this.
StateListDrawable selector = new StateListDrawable();
ColorDrawable clickedColor = new ColorDrawable(colorBar);
ColorDrawable transparent = new ColorDrawable(Color.TRANSPARENT);
selector.addState(new int[]{android.R.attr.state_pressed}, clickedColor);
selector.addState(new int[]{}, transparent);
list.setSelector(selector);
But the HeaderListView doesn't have the setSelector method so I either want to make one, but Im not sure what it does/ how it works, here is the custom list view.
public class HeaderListView extends RelativeLayout {
// TODO: Handle listViews with fast scroll
// TODO: See if there are methods to dispatch to mListView
private static final int FADE_DELAY = 1000;
private static final int FADE_DURATION = 2000;
private InternalListView mListView;
private SectionAdapter mAdapter;
private RelativeLayout mHeader;
private FrameLayout mScrollView;
private AbsListView.OnScrollListener mExternalOnScrollListener;
public HeaderListView(Context context) {
super(context);
init(context, null);
}
public HeaderListView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
private void init(Context context, AttributeSet attrs) {
mListView = new InternalListView(getContext(), attrs);
LayoutParams listParams = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
listParams.addRule(ALIGN_PARENT_TOP);
mListView.setLayoutParams(listParams);
mListView.setOnScrollListener(new HeaderListViewOnScrollListener());
mListView.setVerticalScrollBarEnabled(false);
mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
if (mAdapter != null) {
mAdapter.onItemClick(parent, view, position, id);
}
}
});
addView(mListView);
mHeader = new RelativeLayout(getContext());
LayoutParams headerParams = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
headerParams.addRule(ALIGN_PARENT_TOP);
mHeader.setLayoutParams(headerParams);
mHeader.setGravity(Gravity.BOTTOM);
addView(mHeader);
// The list view's scroll bar can be hidden by the header, so we display our own scroll bar instead
Drawable scrollBarDrawable = getResources().getDrawable(R.drawable.scrollbar_handle_holo_light);
mScrollView = new FrameLayout(getContext());
LayoutParams scrollParams = new LayoutParams(scrollBarDrawable.getIntrinsicWidth(), LayoutParams.MATCH_PARENT);
scrollParams.addRule(ALIGN_PARENT_RIGHT);
scrollParams.rightMargin = (int) dpToPx(2);
mScrollView.setLayoutParams(scrollParams);
ImageView scrollIndicator = new ImageView(context);
scrollIndicator.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
scrollIndicator.setImageDrawable(scrollBarDrawable);
scrollIndicator.setScaleType(ScaleType.FIT_XY);
mScrollView.addView(scrollIndicator);
mScrollView.setVisibility(INVISIBLE);
addView(mScrollView);
}
public void setAdapter(SectionAdapter adapter) {
mAdapter = adapter;
mListView.setAdapter(adapter);
}
public void setOnScrollListener(AbsListView.OnScrollListener l) {
mExternalOnScrollListener = l;
}
private class HeaderListViewOnScrollListener implements AbsListView.OnScrollListener {
private int previousFirstVisibleItem = -1;
private int direction = 0;
private int actualSection = 0;
private boolean scrollingStart = false;
private boolean doneMeasuring = false;
private int lastResetSection = -1;
private int nextH;
private int prevH;
private View previous;
private View next;
private AlphaAnimation fadeOut = new AlphaAnimation(1f, 0f);
private boolean noHeaderUpToHeader = false;
private boolean didScroll = false;
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
if (mExternalOnScrollListener != null) {
mExternalOnScrollListener.onScrollStateChanged(view, scrollState);
}
didScroll = true;
}
#Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
if (mExternalOnScrollListener != null) {
mExternalOnScrollListener.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount);
}
if (!didScroll) {
return;
}
firstVisibleItem -= mListView.getHeaderViewsCount();
if (firstVisibleItem < 0) {
mHeader.removeAllViews();
return;
}
updateScrollBar();
if (visibleItemCount > 0 && firstVisibleItem == 0 && mHeader.getChildAt(0) == null) {
addSectionHeader(0);
lastResetSection = 0;
}
int realFirstVisibleItem = getRealFirstVisibleItem(firstVisibleItem, visibleItemCount);
if (totalItemCount > 0 && previousFirstVisibleItem != realFirstVisibleItem) {
direction = realFirstVisibleItem - previousFirstVisibleItem;
actualSection = mAdapter.getSection(realFirstVisibleItem);
boolean currIsHeader = mAdapter.isSectionHeader(realFirstVisibleItem);
boolean prevHasHeader = mAdapter.hasSectionHeaderView(actualSection - 1);
boolean nextHasHeader = mAdapter.hasSectionHeaderView(actualSection + 1);
boolean currHasHeader = mAdapter.hasSectionHeaderView(actualSection);
boolean currIsLast = mAdapter.getRowInSection(realFirstVisibleItem) == mAdapter.numberOfRows(actualSection) - 1;
boolean prevHasRows = mAdapter.numberOfRows(actualSection - 1) > 0;
boolean currIsFirst = mAdapter.getRowInSection(realFirstVisibleItem) == 0;
boolean needScrolling = currIsFirst && !currHasHeader && prevHasHeader && realFirstVisibleItem != firstVisibleItem;
boolean needNoHeaderUpToHeader = currIsLast && currHasHeader && !nextHasHeader && realFirstVisibleItem == firstVisibleItem && Math.abs(mListView.getChildAt(0).getTop()) >= mListView.getChildAt(0).getHeight() / 2;
noHeaderUpToHeader = false;
if (currIsHeader && !prevHasHeader && firstVisibleItem >= 0) {
resetHeader(direction < 0 ? actualSection - 1 : actualSection);
} else if ((currIsHeader && firstVisibleItem > 0) || needScrolling) {
if (!prevHasRows) {
resetHeader(actualSection-1);
}
startScrolling();
} else if (needNoHeaderUpToHeader) {
noHeaderUpToHeader = true;
} else if (lastResetSection != actualSection) {
resetHeader(actualSection);
}
previousFirstVisibleItem = realFirstVisibleItem;
}
if (scrollingStart) {
int scrolled = realFirstVisibleItem >= firstVisibleItem ? mListView.getChildAt(realFirstVisibleItem - firstVisibleItem).getTop() : 0;
if (!doneMeasuring) {
setMeasurements(realFirstVisibleItem, firstVisibleItem);
}
int headerH = doneMeasuring ? (prevH - nextH) * direction * Math.abs(scrolled) / (direction < 0 ? nextH : prevH) + (direction > 0 ? nextH : prevH) : 0;
mHeader.scrollTo(0, -Math.min(0, scrolled - headerH));
if (doneMeasuring && headerH != mHeader.getLayoutParams().height) {
LayoutParams p = (LayoutParams) (direction < 0 ? next.getLayoutParams() : previous.getLayoutParams());
p.topMargin = headerH - p.height;
mHeader.getLayoutParams().height = headerH;
mHeader.requestLayout();
}
}
if (noHeaderUpToHeader) {
if (lastResetSection != actualSection) {
addSectionHeader(actualSection);
lastResetSection = actualSection + 1;
}
mHeader.scrollTo(0, mHeader.getLayoutParams().height - (mListView.getChildAt(0).getHeight() + mListView.getChildAt(0).getTop()));
}
}
private void startScrolling() {
scrollingStart = true;
doneMeasuring = false;
lastResetSection = -1;
}
private void resetHeader(int section) {
scrollingStart = false;
addSectionHeader(section);
mHeader.requestLayout();
lastResetSection = section;
}
private void setMeasurements(int realFirstVisibleItem, int firstVisibleItem) {
if (direction > 0) {
nextH = realFirstVisibleItem >= firstVisibleItem ? mListView.getChildAt(realFirstVisibleItem - firstVisibleItem).getMeasuredHeight() : 0;
}
previous = mHeader.getChildAt(0);
prevH = previous != null ? previous.getMeasuredHeight() : mHeader.getHeight();
if (direction < 0) {
if (lastResetSection != actualSection - 1) {
addSectionHeader(Math.max(0, actualSection - 1));
next = mHeader.getChildAt(0);
}
nextH = mHeader.getChildCount() > 0 ? mHeader.getChildAt(0).getMeasuredHeight() : 0;
mHeader.scrollTo(0, prevH);
}
doneMeasuring = previous != null && prevH > 0 && nextH > 0;
}
private void updateScrollBar() {
if (mHeader != null && mListView != null && mScrollView != null) {
int offset = mListView.computeVerticalScrollOffset();
int range = mListView.computeVerticalScrollRange();
int extent = mListView.computeVerticalScrollExtent();
mScrollView.setVisibility(extent >= range ? View.INVISIBLE : View.VISIBLE);
if (extent >= range) {
return;
}
int top = range == 0 ? mListView.getHeight() : mListView.getHeight() * offset / range;
int bottom = range == 0 ? 0 : mListView.getHeight() - mListView.getHeight() * (offset + extent) / range;
mScrollView.setPadding(0, top, 0, bottom);
fadeOut.reset();
fadeOut.setFillBefore(true);
fadeOut.setFillAfter(true);
fadeOut.setStartOffset(FADE_DELAY);
fadeOut.setDuration(FADE_DURATION);
mScrollView.clearAnimation();
mScrollView.startAnimation(fadeOut);
}
}
private void addSectionHeader(int actualSection) {
View previousHeader = mHeader.getChildAt(0);
if (previousHeader != null) {
mHeader.removeViewAt(0);
}
if (mAdapter.hasSectionHeaderView(actualSection)) {
View header = mAdapter.getSectionHeaderView(actualSection, null, null);
header.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
header.measure(MeasureSpec.makeMeasureSpec(mHeader.getWidth(), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
mHeader.getLayoutParams().height = header.getMeasuredHeight();
header.scrollTo(0, 0);
mHeader.scrollTo(0, 0);
mHeader.addView(header, 0);
} else {
mHeader.getLayoutParams().height = 0;
mHeader.scrollTo(0, 0);
}
mScrollView.bringToFront();
}
private int getRealFirstVisibleItem(int firstVisibleItem, int visibleItemCount) {
if (visibleItemCount == 0) {
return -1;
}
int relativeIndex = 0, totalHeight = mListView.getChildAt(0).getTop();
for (relativeIndex = 0; relativeIndex < visibleItemCount && totalHeight < mHeader.getHeight(); relativeIndex++) {
totalHeight += mListView.getChildAt(relativeIndex).getHeight();
}
int realFVI = Math.max(firstVisibleItem, firstVisibleItem + relativeIndex - 1);
return realFVI;
}
}
public ListView getListView() {
return mListView;
}
public void addHeaderView(View v) {
mListView.addHeaderView(v);
}
private float dpToPx(float dp) {
return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, getContext().getResources().getDisplayMetrics());
}
protected class InternalListView extends ListView {
public InternalListView(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
protected int computeVerticalScrollExtent() {
return super.computeVerticalScrollExtent();
}
#Override
protected int computeVerticalScrollOffset() {
return super.computeVerticalScrollOffset();
}
#Override
protected int computeVerticalScrollRange() {
return super.computeVerticalScrollRange();
}
}
}
Any help would be appreciate :)
You can set listView.setSelector(selector); inside init() method in the HeaderListView class.
Related
I have this RecyclerView where any item can be clicked to open, and when it's opened, it grows (picture 1 is a closed item, picture 2 is an opened item)
The layout file of an item holds two states - the closed card, and the opened card. To switch between them, I change only the visibility of any state. Here are the methods that control an item's opening (expanding) or closing (shrinking) :
/**
* handles the expanding functionality.
*/
public void expand() {
shrink = true;
if (expandedItem != -1) {
notifyItemChanged(expandedItem);
}
expandedItem = getLayoutPosition();
toggleExpandShrinkIcon(true);
if (!openedFromParent[1]) {
openedFromParent[1] = true;
} else {
openedFromParent[0] = false;
}
expandedContainer.setVisibility(View.VISIBLE);
shrunkProgressBar.setVisibility(View.INVISIBLE);
}
/**
* handles the shrinking functionality.
*/
public void shrink() {
toggleExpandShrinkIcon(false);
expandedContainer.setVisibility(View.GONE);
shrunkProgressBar.setVisibility(View.VISIBLE);
shrink = false;
}
These methods are located in the RecyclerView's adapter, inside of the ViewHolder's class, and they are public so I could use them also out of the RecyclerView's adapter class (not only by clicking), as I did when one item hovers another.
Recently I added drag-to-hover functionality (using this library) so that I can drag any item on top of any other item, and when one item hovers another item, the lower item gets opened.
When an item gets opened, it pushes all the other items below it to be able to expand without hiding the items under it (like in the first video).
When moving from hovering one item to another, say from the second item to the third, when hovering the second item it gets opened and the third item is pushed down, and when moving to the third item the second item gets closed, but the third item won't go back up.
Then when hovering the third item, it gets opened on the fourth item (see the second video to understand better).
Here's the code in the class that handles the hovering action:
public class HoveringCallback extends ItemTouchHelper.SimpleCallback {
//re-used list for selecting a swap target
private List<RecyclerView.ViewHolder> swapTargets = new ArrayList<>();
//re used for for sorting swap targets
private List<Integer> distances = new ArrayList<>();
private float selectedStartX;
private float selectedStartY;
public interface OnDroppedListener {
void onDroppedOn(ActiveGoalsAdapter.ActiveGoalsViewHolder viewHolder, ActiveGoalsAdapter.ActiveGoalsViewHolder target);
}
private List<OnDroppedListener> onDroppedListeners = new ArrayList<>();
#Nullable
private RecyclerView recyclerView;
#Nullable
ActiveGoalsAdapter.ActiveGoalsViewHolder selected;
#Nullable
private ActiveGoalsAdapter.ActiveGoalsViewHolder hovered;
ItemBackgroundCallback backgroundCallback;
public HoveringCallback() {
super(ItemTouchHelper.UP | ItemTouchHelper.DOWN, 0);
}
public void attachToRecyclerView(#Nullable RecyclerView recyclerView) {
this.recyclerView = recyclerView;
}
public void addOnDropListener(OnDroppedListener listener) {
onDroppedListeners.add(listener);
}
public void removeOnDropListener(OnDroppedListener listener) {
onDroppedListeners.remove(listener);
}
#Override
public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) {
super.onSelectedChanged(viewHolder, actionState);
if (viewHolder == null) {
if (hovered != null) {
notifyDroppedOnListeners(hovered);
}
} else {
selectedStartX = viewHolder.itemView.getLeft();
selectedStartY = viewHolder.itemView.getTop();
}
this.selected = (ActiveGoalsAdapter.ActiveGoalsViewHolder) viewHolder;
if (actionState != ItemTouchHelper.ACTION_STATE_IDLE && viewHolder != null) {
viewHolder.itemView.setBackgroundColor(backgroundCallback.getDraggingBackgroundColor(viewHolder));
}
}
private void notifyDroppedOnListeners(ActiveGoalsAdapter.ActiveGoalsViewHolder holder) {
for (OnDroppedListener listener : onDroppedListeners) {
listener.onDroppedOn(selected, (ActiveGoalsAdapter.ActiveGoalsViewHolder) holder);
}
}
#Override
public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
super.clearView(recyclerView, viewHolder);
viewHolder.itemView.setBackgroundColor(backgroundCallback.getDefaultBackgroundColor(viewHolder));
}
#Override
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
return false;
}
#Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
}
#Override
public void onChildDraw(Canvas canvas, RecyclerView parent, RecyclerView.ViewHolder viewHolder,
float dX, float dY, int actionState, boolean isCurrentlyActive) {
super.onChildDraw(canvas, parent, viewHolder, dX, dY, actionState, isCurrentlyActive);
if (actionState != ItemTouchHelper.ACTION_STATE_DRAG) {
return;
}
if (recyclerView == null || selected == null) {
return;
}
final RecyclerView.LayoutManager lm = recyclerView.getLayoutManager();
final int childCount = lm.getChildCount();
List<RecyclerView.ViewHolder> swapTargets = findSwapTargets((ActiveGoalsAdapter.ActiveGoalsViewHolder) viewHolder, dX, dY);
final int x = (int) (selectedStartX + dX);
final int y = (int) (selectedStartY + dY);
hovered = (ActiveGoalsAdapter.ActiveGoalsViewHolder) chooseDropTarget((ActiveGoalsAdapter.ActiveGoalsViewHolder) viewHolder, swapTargets, x, y);
if (hovered == null) {
this.swapTargets.clear();
this.distances.clear();
}
for (int i = 0; i < childCount; i++) {
final View child = lm.getChildAt(i);
if (viewHolder.itemView == child) {
continue;
}
ActiveGoalsAdapter.ActiveGoalsViewHolder childViewHolder = (ActiveGoalsAdapter.ActiveGoalsViewHolder) parent.findContainingViewHolder(child);
if (childViewHolder == null || childViewHolder.getAdapterPosition() == RecyclerView.NO_POSITION) {
continue;
}
final int count = canvas.save();
if (childViewHolder == hovered) {
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
childViewHolder.expand();
}
}, 500);
} else {
if(!childViewHolder.isShrunk()) {
childViewHolder.shrink();
if(canvas.getSaveCount() != count) {
canvas.restoreToCount(count);
}
}
}
}
}
private List<RecyclerView.ViewHolder> findSwapTargets(ActiveGoalsAdapter.ActiveGoalsViewHolder viewHolder, float dX, float dY) {
swapTargets.clear();
distances.clear();
final int margin = getBoundingBoxMargin();
final int left = Math.round(selectedStartX + dX) - margin;
final int top = Math.round(selectedStartY + dY) - margin;
final int right = left + viewHolder.itemView.getWidth() + 2 * margin;
final int bottom = top + viewHolder.itemView.getHeight() + 2 * margin;
final int centerX = (left + right) / 2;
final int centerY = (top + bottom) / 2;
final RecyclerView.LayoutManager lm = recyclerView.getLayoutManager();
final int childCount = lm.getChildCount();
for (int i = 0; i < childCount; i++) {
View other = lm.getChildAt(i);
if (other == viewHolder.itemView) {
continue; //myself!
}
if (other.getBottom() < top || other.getTop() > bottom
|| other.getRight() < left || other.getLeft() > right) {
continue;
}
final ActiveGoalsAdapter.ActiveGoalsViewHolder otherVh = (ActiveGoalsAdapter.ActiveGoalsViewHolder) recyclerView.getChildViewHolder(other);
if (canDropOver(recyclerView, selected, otherVh)) {
// find the index to add
final int dx = Math.abs(centerX - (other.getLeft() + other.getRight()) / 2);
final int dy = Math.abs(centerY - (other.getTop() + other.getBottom()) / 2);
final int dist = dx * dx + dy * dy;
int pos = 0;
final int cnt = swapTargets.size();
for (int j = 0; j < cnt; j++) {
if (dist > distances.get(j)) {
pos++;
} else {
break;
}
}
swapTargets.add(pos, otherVh);
distances.add(pos, dist);
}
}
return swapTargets;
}
#Override
public float getMoveThreshold(RecyclerView.ViewHolder viewHolder) {
return 0.05f;
}
#Override
public RecyclerView.ViewHolder chooseDropTarget(RecyclerView.ViewHolder selected,
List<RecyclerView.ViewHolder> dropTargets,
int curX, int curY) {
int right = curX + selected.itemView.getWidth();
int bottom = curY + selected.itemView.getHeight();
ActiveGoalsAdapter.ActiveGoalsViewHolder winner = null;
int winnerScore = -1;
final int dx = curX - selected.itemView.getLeft();
final int dy = curY - selected.itemView.getTop();
final int targetsSize = dropTargets.size();
for (int i = 0; i < targetsSize; i++) {
final ActiveGoalsAdapter.ActiveGoalsViewHolder target = (ActiveGoalsAdapter.ActiveGoalsViewHolder) dropTargets.get(i);
if (dx > 0) {
int diff = target.itemView.getRight() - right;
if (diff < 0 && target.itemView.getRight() > selected.itemView.getRight()) {
final int score = Math.abs(diff);
if (score > winnerScore) {
winnerScore = score;
winner = target;
}
}
}
if (dx < 0) {
int diff = target.itemView.getLeft() - curX;
if (diff > 0 && target.itemView.getLeft() < selected.itemView.getLeft()) {
final int score = Math.abs(diff);
if (score > winnerScore) {
winnerScore = score;
winner = target;
}
}
}
if (dy < 0) {
int diff = target.itemView.getTop() - curY;
if (target.itemView.getTop() < selected.itemView.getTop()) {
final int score = Math.abs(diff);
if (score > winnerScore) {
winnerScore = score;
winner = target;
}
}
}
if (dy > 0) {
int diff = target.itemView.getBottom() - bottom;
if (target.itemView.getBottom() > selected.itemView.getBottom()) {
final int score = Math.abs(diff);
if (score > winnerScore) {
winnerScore = score;
winner = target;
}
}
}
}
return winner;
}
}
How can I solve this? (make the third item go back up, like when the drag is released, at the end of the second video)
Help would be highly appreciated! (:
First picture (closed item):
Second picture (opened item):
First Video (item gets opened & closed in the list):
Second video (item dragged):
Without studying the code of Library I assume positions are being changed so it is highly likely that .notifyItemChanged(position) is invoked, and in that, expand function invoke because there is no check to isolate the expand function from unhandled events
A simple answer can be to save the state and disable the expansion of items
Set isDragActive to true when where the item is long pressed
private static isDragActive = false
#Overrides
public onCreateViewHolder(){
itemView.setOnItemLongClickListener(new OnItemLongClickListener() {
#Override
public boolean onItemLongClick(AdapterView<?> arg0, View arg1,
int pos, long id) {
isDragActive = true;
return true;
}
});
}
public void expand() {
if(!isDragActive){
shrink = true;
if (expandedItem != -1) {
notifyItemChanged(expandedItem);
}
expandedItem = getLayoutPosition();
toggleExpandShrinkIcon(true);
if (!openedFromParent[1]) {
openedFromParent[1] = true;
} else {
openedFromParent[0] = false;
}
expandedContainer.setVisibility(View.VISIBLE);
shrunkProgressBar.setVisibility(View.INVISIBLE);
}
}
You can also use the library given below instead of above solution
https://github.com/mikepenz/FastAdapter
I think the best bet is to set your code that opens each recyclerview using ACTION_UP so that the code is triggered on the lifting of the finger, not on the sensing of it.
button.setOnTouchListener(new View.OnTouchListener() { #Override public boolean onTouch(View view, MotionEvent motionEvent) { sw
If(MotionEvent.ACTION_UP) {Toast.makeText(MainActivity.this, "Up", Toast.LENGTH_SHORT).show(); }
I am trying to figure out what the field itemView actually is. Here is the code snippet I taken from the documentation.
public abstract static class ViewHolder {
#NonNull
public final View itemView;
// others removed for simplicity
}
If we extend ViewHolder class, as we also need to specify some fields of type that is derived from View to hold our own widgets for each list item, it is clear that itemView will not hold our own widgets.
Question
What does itemView field reference? I am confused whether it references either RecyclerView or the root view of list_item_layout.xml (that contains our own widgets). Could you clarify which one?
Edit
For those who want to know the internal details of ViewHolder, see the following. I present the complete one.
public abstract static class ViewHolder {
#NonNull
public final View itemView;
WeakReference<RecyclerView> mNestedRecyclerView;
int mPosition = -1;
int mOldPosition = -1;
long mItemId = -1L;
int mItemViewType = -1;
int mPreLayoutPosition = -1;
RecyclerView.ViewHolder mShadowedHolder = null;
RecyclerView.ViewHolder mShadowingHolder = null;
static final int FLAG_BOUND = 1;
static final int FLAG_UPDATE = 2;
static final int FLAG_INVALID = 4;
static final int FLAG_REMOVED = 8;
static final int FLAG_NOT_RECYCLABLE = 16;
static final int FLAG_RETURNED_FROM_SCRAP = 32;
static final int FLAG_IGNORE = 128;
static final int FLAG_TMP_DETACHED = 256;
static final int FLAG_ADAPTER_POSITION_UNKNOWN = 512;
static final int FLAG_ADAPTER_FULLUPDATE = 1024;
static final int FLAG_MOVED = 2048;
static final int FLAG_APPEARED_IN_PRE_LAYOUT = 4096;
static final int PENDING_ACCESSIBILITY_STATE_NOT_SET = -1;
static final int FLAG_BOUNCED_FROM_HIDDEN_LIST = 8192;
static final int FLAG_SET_A11Y_ITEM_DELEGATE = 16384;
int mFlags;
private static final List<Object> FULLUPDATE_PAYLOADS = Collections.emptyList();
List<Object> mPayloads = null;
List<Object> mUnmodifiedPayloads = null;
private int mIsRecyclableCount = 0;
RecyclerView.Recycler mScrapContainer = null;
boolean mInChangeScrap = false;
private int mWasImportantForAccessibilityBeforeHidden = 0;
#VisibleForTesting
int mPendingAccessibilityState = -1;
RecyclerView mOwnerRecyclerView;
public ViewHolder(#NonNull View itemView) {
if (itemView == null) {
throw new IllegalArgumentException("itemView may not be null");
} else {
this.itemView = itemView;
}
}
void flagRemovedAndOffsetPosition(int mNewPosition, int offset, boolean applyToPreLayout) {
this.addFlags(8);
this.offsetPosition(offset, applyToPreLayout);
this.mPosition = mNewPosition;
}
void offsetPosition(int offset, boolean applyToPreLayout) {
if (this.mOldPosition == -1) {
this.mOldPosition = this.mPosition;
}
if (this.mPreLayoutPosition == -1) {
this.mPreLayoutPosition = this.mPosition;
}
if (applyToPreLayout) {
this.mPreLayoutPosition += offset;
}
this.mPosition += offset;
if (this.itemView.getLayoutParams() != null) {
((RecyclerView.LayoutParams)this.itemView.getLayoutParams()).mInsetsDirty = true;
}
}
void clearOldPosition() {
this.mOldPosition = -1;
this.mPreLayoutPosition = -1;
}
void saveOldPosition() {
if (this.mOldPosition == -1) {
this.mOldPosition = this.mPosition;
}
}
boolean shouldIgnore() {
return (this.mFlags & 128) != 0;
}
/** #deprecated */
#Deprecated
public final int getPosition() {
return this.mPreLayoutPosition == -1 ? this.mPosition : this.mPreLayoutPosition;
}
public final int getLayoutPosition() {
return this.mPreLayoutPosition == -1 ? this.mPosition : this.mPreLayoutPosition;
}
public final int getAdapterPosition() {
return this.mOwnerRecyclerView == null ? -1 : this.mOwnerRecyclerView.getAdapterPositionFor(this);
}
public final int getOldPosition() {
return this.mOldPosition;
}
public final long getItemId() {
return this.mItemId;
}
public final int getItemViewType() {
return this.mItemViewType;
}
boolean isScrap() {
return this.mScrapContainer != null;
}
void unScrap() {
this.mScrapContainer.unscrapView(this);
}
boolean wasReturnedFromScrap() {
return (this.mFlags & 32) != 0;
}
void clearReturnedFromScrapFlag() {
this.mFlags &= -33;
}
void clearTmpDetachFlag() {
this.mFlags &= -257;
}
void stopIgnoring() {
this.mFlags &= -129;
}
void setScrapContainer(RecyclerView.Recycler recycler, boolean isChangeScrap) {
this.mScrapContainer = recycler;
this.mInChangeScrap = isChangeScrap;
}
boolean isInvalid() {
return (this.mFlags & 4) != 0;
}
boolean needsUpdate() {
return (this.mFlags & 2) != 0;
}
boolean isBound() {
return (this.mFlags & 1) != 0;
}
boolean isRemoved() {
return (this.mFlags & 8) != 0;
}
boolean hasAnyOfTheFlags(int flags) {
return (this.mFlags & flags) != 0;
}
boolean isTmpDetached() {
return (this.mFlags & 256) != 0;
}
boolean isAdapterPositionUnknown() {
return (this.mFlags & 512) != 0 || this.isInvalid();
}
void setFlags(int flags, int mask) {
this.mFlags = this.mFlags & ~mask | flags & mask;
}
void addFlags(int flags) {
this.mFlags |= flags;
}
void addChangePayload(Object payload) {
if (payload == null) {
this.addFlags(1024);
} else if ((this.mFlags & 1024) == 0) {
this.createPayloadsIfNeeded();
this.mPayloads.add(payload);
}
}
private void createPayloadsIfNeeded() {
if (this.mPayloads == null) {
this.mPayloads = new ArrayList();
this.mUnmodifiedPayloads = Collections.unmodifiableList(this.mPayloads);
}
}
void clearPayload() {
if (this.mPayloads != null) {
this.mPayloads.clear();
}
this.mFlags &= -1025;
}
List<Object> getUnmodifiedPayloads() {
if ((this.mFlags & 1024) == 0) {
return this.mPayloads != null && this.mPayloads.size() != 0 ? this.mUnmodifiedPayloads : FULLUPDATE_PAYLOADS;
} else {
return FULLUPDATE_PAYLOADS;
}
}
void resetInternal() {
this.mFlags = 0;
this.mPosition = -1;
this.mOldPosition = -1;
this.mItemId = -1L;
this.mPreLayoutPosition = -1;
this.mIsRecyclableCount = 0;
this.mShadowedHolder = null;
this.mShadowingHolder = null;
this.clearPayload();
this.mWasImportantForAccessibilityBeforeHidden = 0;
this.mPendingAccessibilityState = -1;
RecyclerView.clearNestedRecyclerViewIfNotNested(this);
}
void onEnteredHiddenState(RecyclerView parent) {
if (this.mPendingAccessibilityState != -1) {
this.mWasImportantForAccessibilityBeforeHidden = this.mPendingAccessibilityState;
} else {
this.mWasImportantForAccessibilityBeforeHidden = ViewCompat.getImportantForAccessibility(this.itemView);
}
parent.setChildImportantForAccessibilityInternal(this, 4);
}
void onLeftHiddenState(RecyclerView parent) {
parent.setChildImportantForAccessibilityInternal(this, this.mWasImportantForAccessibilityBeforeHidden);
this.mWasImportantForAccessibilityBeforeHidden = 0;
}
public String toString() {
StringBuilder sb = new StringBuilder("ViewHolder{" + Integer.toHexString(this.hashCode()) + " position=" + this.mPosition + " id=" + this.mItemId + ", oldPos=" + this.mOldPosition + ", pLpos:" + this.mPreLayoutPosition);
if (this.isScrap()) {
sb.append(" scrap ").append(this.mInChangeScrap ? "[changeScrap]" : "[attachedScrap]");
}
if (this.isInvalid()) {
sb.append(" invalid");
}
if (!this.isBound()) {
sb.append(" unbound");
}
if (this.needsUpdate()) {
sb.append(" update");
}
if (this.isRemoved()) {
sb.append(" removed");
}
if (this.shouldIgnore()) {
sb.append(" ignored");
}
if (this.isTmpDetached()) {
sb.append(" tmpDetached");
}
if (!this.isRecyclable()) {
sb.append(" not recyclable(" + this.mIsRecyclableCount + ")");
}
if (this.isAdapterPositionUnknown()) {
sb.append(" undefined adapter position");
}
if (this.itemView.getParent() == null) {
sb.append(" no parent");
}
sb.append("}");
return sb.toString();
}
public final void setIsRecyclable(boolean recyclable) {
this.mIsRecyclableCount = recyclable ? this.mIsRecyclableCount - 1 : this.mIsRecyclableCount + 1;
if (this.mIsRecyclableCount < 0) {
this.mIsRecyclableCount = 0;
Log.e("View", "isRecyclable decremented below 0: unmatched pair of setIsRecyable() calls for " + this);
} else if (!recyclable && this.mIsRecyclableCount == 1) {
this.mFlags |= 16;
} else if (recyclable && this.mIsRecyclableCount == 0) {
this.mFlags &= -17;
}
}
public final boolean isRecyclable() {
return (this.mFlags & 16) == 0 && !ViewCompat.hasTransientState(this.itemView);
}
boolean shouldBeKeptAsChild() {
return (this.mFlags & 16) != 0;
}
boolean doesTransientStatePreventRecycling() {
return (this.mFlags & 16) == 0 && ViewCompat.hasTransientState(this.itemView);
}
boolean isUpdated() {
return (this.mFlags & 2) != 0;
}
}
It totally depends on your code as it refers the itemView when you calls new SomeViewHolder(itemView).
For example, in the code of Android Guide - Create a List with RecyclerView - Add a list adapter MyViewHolder have a reference of a TextView v after created.
public MyAdapter.MyViewHolder onCreateViewHolder(ViewGroup parent,
int viewType) {
// create a new view
TextView v = (TextView) LayoutInflater.from(parent.getContext())
.inflate(R.layout.my_text_view, parent, false);
...
MyViewHolder vh = new MyViewHolder(v);
return vh;
}
I created a ListView with pinned which works well once. But when I arrive latest item of ListView crashes on me and says NullPointerException.
I need that when I arrive latest item get next 5 of items .
MainActivity.java :
public class PinnedSectionListActivity extends ListActivity implements OnClickListener {
public static Context context;
public static Context _context;
public static SQLiteDatabase sql;
public Cursor cursor;
public Cursor cursorA;
public boolean flag = false;
public Integer count = 0;
public List<Items> result;
public List<ItemsT> resultT;
static class SimpleAdapter extends ArrayAdapter<Item> implements PinnedSectionListView.PinnedSectionListAdapter {
private List<Items> Results;
private List<ItemsT> ResultsT;
private Integer counts;
private static final int[] COLORS = new int[] {
R.color.green_light, R.color.orange_light,
R.color.blue_light, R.color.red_light };
public SimpleAdapter(Context context,List<Items> MResults,List<ItemsT> MresultT,Integer count , int resource, int textViewResourceId) {
super(context, resource, textViewResourceId);
this.Results = MResults;
this.ResultsT = MresultT;
this.counts = count;
generateDataset(false);
}
public void generateDataset(boolean clear) {
int y = 0;
if (clear) clear();
prepareSections(counts);
int sectionPosition = 0, listPosition = 0;
for (char i=0; i<counts; i++) {
final Items ci = Results.get(i);
Item section = new Item(Item.SECTION, ci.WTitleO);
section.sectionPosition = sectionPosition;
section.listPosition = listPosition++;
onSectionAdded(section, sectionPosition);
add(section);
for (int j=0;j<ci.count;j++) {
final ItemsT xx = ResultsT.get(y);
Item item = new Item(Item.ITEM, xx.WTitleT);
item.sectionPosition = sectionPosition;
item.listPosition = listPosition++;
y++;
add(item);
}
sectionPosition++;
}
}
protected void prepareSections(int sectionsNumber) { }
protected void onSectionAdded(Item section, int sectionPosition) { }
#Override public View getView(int position, View convertView, ViewGroup parent) {
TextView view = (TextView) super.getView(position, convertView, parent);
view.setTextColor(Color.DKGRAY);
view.setTag("" + position);
Item item = getItem(position);
if (item.type == Item.SECTION) {
view.setBackgroundColor(parent.getResources().getColor(COLORS[item.sectionPosition % COLORS.length]));
}
return view;
}
#Override public int getViewTypeCount() {
return 2;
}
#Override public int getItemViewType(int position) {
return getItem(position).type;
}
#Override
public boolean isItemViewTypePinned(int viewType) {
return viewType == Item.SECTION;
}
}
static class Items {
public int conO ;
public String WTitleO;
public int count;
}
static class ItemsT {
public String WTitleT;
}
static class Item {
public static final int ITEM = 0;
public static final int SECTION = 1;
public final int type;
public final String text;
public int sectionPosition;
public int listPosition;
public Item(int type, String text) {
this.type = type;
this.text = text;
}
#Override public String toString() {
return text;
}
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
context = getApplicationContext();
_context = this;
DB db = new DB(context);
db.CreateFile();
try {
db.CreateandOpenDataBase();
} catch (IOException e) {
e.printStackTrace();
}
sql = db.openDataBase();
test();
}
public void test(){
result = new ArrayList<Items>();
resultT = new ArrayList<ItemsT>();
try {
cursor = sql.rawQuery("SELECT (select count() as number from WebSite_BookPageDB22 as b where b.ParentID = a.ContentID) as count" +
" ,ContentID,Title" +
" FROM WebSite_BookPageDB22 as a" +
" WHERE ArticleID = '" + 61799 + "' AND ParentID = '" + 0 + "'order by PageID ASC LIMIT 5", null);
if (cursor != null) {
if (cursor.moveToFirst()) {
do {
Items ci = new Items();
ci.conO = cursor.getInt(cursor.getColumnIndex("ContentID"));
ci.WTitleO = cursor.getString(cursor.getColumnIndex("Title"));
ci.count = cursor.getInt(cursor.getColumnIndex("count"));
if(flag == false) {
count = cursor.getCount();
flag = true;
}
cursorA = sql.rawQuery("select Title from WebSite_BookPageDB22 where ParentID = '" + ci.conO +"' order by PageID ASC", null);
try {
if (cursorA != null && cursorA.moveToFirst()) {
do {
ItemsT cty = new ItemsT();
cty.WTitleT = cursorA.getString(cursorA.getColumnIndex("Title"));
resultT.add(cty);
} while (cursorA.moveToNext());
}
}catch (Exception e){
Log.i("xxx", "You have an error");
}finally {
if (cursorA != null) {
cursorA.close();
}
}
result.add(ci);
} while (cursor.moveToNext());
}
}
} catch (Exception e) {
} finally {
cursor.close();
}
SimpleAdapter adapt = new SimpleAdapter(_context, result, resultT, count, android.R.layout.simple_list_item_1, android.R.id.text1);
setListAdapter(adapt);
}
#Override
protected void onListItemClick(ListView l, View v, int position, long id) {
Item item = (Item) getListView().getAdapter().getItem(position);
if (item != null) {
Toast.makeText(this, "Item " + position + ": " + item.text, Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(this, "Item " + position, Toast.LENGTH_SHORT).show();
}
}
#Override
public void onClick(View v) {
Toast.makeText(this, "Item: " + v.getTag() , Toast.LENGTH_SHORT).show();
}
}
And PinnedSectionListView.java :
public class PinnedSectionListView extends ListView {
//-- inner classes
/** List adapter to be implemented for being used with PinnedSectionListView adapter. */
public static interface PinnedSectionListAdapter extends ListAdapter {
/** This method shall return 'true' if views of given type has to be pinned. */
boolean isItemViewTypePinned(int viewType);
}
/** Wrapper class for pinned section view and its position in the list. */
static class PinnedSection {
public View view;
public int position;
public long id;
}
//-- class fields
// fields used for handling touch events
private final Rect mTouchRect = new Rect();
private final PointF mTouchPoint = new PointF();
private int mTouchSlop;
private View mTouchTarget;
private MotionEvent mDownEvent;
// fields used for drawing shadow under a pinned section
private GradientDrawable mShadowDrawable;
private int mSectionsDistanceY;
private int mShadowHeight;
/** Delegating listener, can be null. */
OnScrollListener mDelegateOnScrollListener;
/** Shadow for being recycled, can be null. */
PinnedSection mRecycleSection;
/** shadow instance with a pinned view, can be null. */
PinnedSection mPinnedSection;
/** Pinned view Y-translation. We use it to stick pinned view to the next section. */
int mTranslateY;
/** Scroll listener which does the magic */
private final OnScrollListener mOnScrollListener = new OnScrollListener() {
#Override public void onScrollStateChanged(AbsListView view, int scrollState) {
if (mDelegateOnScrollListener != null) { // delegate
mDelegateOnScrollListener.onScrollStateChanged(view, scrollState);
}
}
#Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
if (mDelegateOnScrollListener != null) { // delegate
mDelegateOnScrollListener.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount);
}
if(firstVisibleItem+visibleItemCount == totalItemCount && totalItemCount!=0)
{
PinnedSectionListActivity xx = new PinnedSectionListActivity();
xx.flag = false;
xx.test();
/*Intent refresh = new Intent(PinnedSectionListActivity._context, PinnedSectionListActivity.class);
PinnedSectionListActivity._context.startActivity(refresh);*/
}
// get expected adapter or fail fast
ListAdapter adapter = getAdapter();
if (adapter == null || visibleItemCount == 0) return; // nothing to do
final boolean isFirstVisibleItemSection =
isItemViewTypePinned(adapter, adapter.getItemViewType(firstVisibleItem));
if (isFirstVisibleItemSection) {
View sectionView = getChildAt(0);
if (sectionView.getTop() == getPaddingTop()) { // view sticks to the top, no need for pinned shadow
destroyPinnedShadow();
} else { // section doesn't stick to the top, make sure we have a pinned shadow
ensureShadowForPosition(firstVisibleItem, firstVisibleItem, visibleItemCount);
}
} else { // section is not at the first visible position
int sectionPosition = findCurrentSectionPosition(firstVisibleItem);
if (sectionPosition > -1) { // we have section position
ensureShadowForPosition(sectionPosition, firstVisibleItem, visibleItemCount);
} else { // there is no section for the first visible item, destroy shadow
destroyPinnedShadow();
}
}
};
};
/** Default change observer. */
private final DataSetObserver mDataSetObserver = new DataSetObserver() {
#Override public void onChanged() {
recreatePinnedShadow();
};
#Override public void onInvalidated() {
recreatePinnedShadow();
}
};
//-- constructors
public PinnedSectionListView(Context context, AttributeSet attrs) {
super(context, attrs);
initView();
}
public PinnedSectionListView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initView();
}
private void initView() {
setOnScrollListener(mOnScrollListener);
mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
initShadow(true);
}
//-- public API methods
public void setShadowVisible(boolean visible) {
initShadow(visible);
if (mPinnedSection != null) {
View v = mPinnedSection.view;
invalidate(v.getLeft(), v.getTop(), v.getRight(), v.getBottom() + mShadowHeight);
}
}
//-- pinned section drawing methods
public void initShadow(boolean visible) {
if (visible) {
if (mShadowDrawable == null) {
mShadowDrawable = new GradientDrawable(Orientation.TOP_BOTTOM,
new int[] { Color.parseColor("#ffa0a0a0"), Color.parseColor("#50a0a0a0"), Color.parseColor("#00a0a0a0")});
mShadowHeight = (int) (8 * getResources().getDisplayMetrics().density);
}
} else {
if (mShadowDrawable != null) {
mShadowDrawable = null;
mShadowHeight = 0;
}
}
}
/** Create shadow wrapper with a pinned view for a view at given position */
void createPinnedShadow(int position) {
// try to recycle shadow
PinnedSection pinnedShadow = mRecycleSection;
mRecycleSection = null;
// create new shadow, if needed
if (pinnedShadow == null) pinnedShadow = new PinnedSection();
// request new view using recycled view, if such
View pinnedView = getAdapter().getView(position, pinnedShadow.view, PinnedSectionListView.this);
// read layout parameters
LayoutParams layoutParams = (LayoutParams) pinnedView.getLayoutParams();
if (layoutParams == null) {
layoutParams = (LayoutParams) generateDefaultLayoutParams();
pinnedView.setLayoutParams(layoutParams);
}
int heightMode = MeasureSpec.getMode(layoutParams.height);
int heightSize = MeasureSpec.getSize(layoutParams.height);
if (heightMode == MeasureSpec.UNSPECIFIED) heightMode = MeasureSpec.EXACTLY;
int maxHeight = getHeight() - getListPaddingTop() - getListPaddingBottom();
if (heightSize > maxHeight) heightSize = maxHeight;
// measure & layout
int ws = MeasureSpec.makeMeasureSpec(getWidth() - getListPaddingLeft() - getListPaddingRight(), MeasureSpec.EXACTLY);
int hs = MeasureSpec.makeMeasureSpec(heightSize, heightMode);
pinnedView.measure(ws, hs);
pinnedView.layout(0, 0, pinnedView.getMeasuredWidth(), pinnedView.getMeasuredHeight());
mTranslateY = 0;
// initialize pinned shadow
pinnedShadow.view = pinnedView;
pinnedShadow.position = position;
pinnedShadow.id = getAdapter().getItemId(position);
// store pinned shadow
mPinnedSection = pinnedShadow;
}
/** Destroy shadow wrapper for currently pinned view */
void destroyPinnedShadow() {
if (mPinnedSection != null) {
// keep shadow for being recycled later
mRecycleSection = mPinnedSection;
mPinnedSection = null;
}
}
/** Makes sure we have an actual pinned shadow for given position. */
void ensureShadowForPosition(int sectionPosition, int firstVisibleItem, int visibleItemCount) {
if (visibleItemCount < 2) { // no need for creating shadow at all, we have a single visible item
destroyPinnedShadow();
return;
}
if (mPinnedSection != null
&& mPinnedSection.position != sectionPosition) { // invalidate shadow, if required
destroyPinnedShadow();
}
if (mPinnedSection == null) { // create shadow, if empty
createPinnedShadow(sectionPosition);
}
// align shadow according to next section position, if needed
int nextPosition = sectionPosition + 1;
if (nextPosition < getCount()) {
int nextSectionPosition = findFirstVisibleSectionPosition(nextPosition,
visibleItemCount - (nextPosition - firstVisibleItem));
if (nextSectionPosition > -1) {
View nextSectionView = getChildAt(nextSectionPosition - firstVisibleItem);
final int bottom = mPinnedSection.view.getBottom() + getPaddingTop();
mSectionsDistanceY = nextSectionView.getTop() - bottom;
if (mSectionsDistanceY < 0) {
// next section overlaps pinned shadow, move it up
mTranslateY = mSectionsDistanceY;
} else {
// next section does not overlap with pinned, stick to top
mTranslateY = 0;
}
} else {
// no other sections are visible, stick to top
mTranslateY = 0;
mSectionsDistanceY = Integer.MAX_VALUE;
}
}
}
int findFirstVisibleSectionPosition(int firstVisibleItem, int visibleItemCount) {
ListAdapter adapter = getAdapter();
int adapterDataCount = adapter.getCount();
if (getLastVisiblePosition() >= adapterDataCount) return -1; // dataset has changed, no candidate
if (firstVisibleItem+visibleItemCount >= adapterDataCount){//added to prevent index Outofbound (in case)
visibleItemCount = adapterDataCount-firstVisibleItem;
}
for (int childIndex = 0; childIndex < visibleItemCount; childIndex++) {
int position = firstVisibleItem + childIndex;
int viewType = adapter.getItemViewType(position);
if (isItemViewTypePinned(adapter, viewType)) return position;
}
return -1;
}
int findCurrentSectionPosition(int fromPosition) {
ListAdapter adapter = getAdapter();
if (fromPosition >= adapter.getCount()) return -1; // dataset has changed, no candidate
if (adapter instanceof SectionIndexer) {
// try fast way by asking section indexer
SectionIndexer indexer = (SectionIndexer) adapter;
int sectionPosition = indexer.getSectionForPosition(fromPosition);
int itemPosition = indexer.getPositionForSection(sectionPosition);
int typeView = adapter.getItemViewType(itemPosition);
if (isItemViewTypePinned(adapter, typeView)) {
return itemPosition;
} // else, no luck
}
// try slow way by looking through to the next section item above
for (int position=fromPosition; position>=0; position--) {
int viewType = adapter.getItemViewType(position);
if (isItemViewTypePinned(adapter, viewType)) return position;
}
return -1; // no candidate found
}
void recreatePinnedShadow() {
destroyPinnedShadow();
ListAdapter adapter = getAdapter();
if (adapter != null && adapter.getCount() > 0) {
int firstVisiblePosition = getFirstVisiblePosition();
int sectionPosition = findCurrentSectionPosition(firstVisiblePosition);
if (sectionPosition == -1) return; // no views to pin, exit
ensureShadowForPosition(sectionPosition,
firstVisiblePosition, getLastVisiblePosition() - firstVisiblePosition);
}
}
#Override
public void setOnScrollListener(OnScrollListener listener) {
if (listener == mOnScrollListener) {
super.setOnScrollListener(listener);
} else {
mDelegateOnScrollListener = listener;
}
}
#Override
public void onRestoreInstanceState(Parcelable state) {
super.onRestoreInstanceState(state);
post(new Runnable() {
#Override public void run() { // restore pinned view after configuration change
recreatePinnedShadow();
}
});
}
#Override
public void setAdapter(ListAdapter adapter) {
// assert adapter in debug mode
if (BuildConfig.DEBUG && adapter != null) {
if (!(adapter instanceof PinnedSectionListAdapter))
throw new IllegalArgumentException("Does your adapter implement PinnedSectionListAdapter?");
if (adapter.getViewTypeCount() < 2)
throw new IllegalArgumentException("Does your adapter handle at least two types" +
" of views in getViewTypeCount() method: items and sections?");
}
// unregister observer at old adapter and register on new one
ListAdapter oldAdapter = getAdapter();
if (oldAdapter != null) oldAdapter.unregisterDataSetObserver(mDataSetObserver);
if (adapter != null) adapter.registerDataSetObserver(mDataSetObserver);
// destroy pinned shadow, if new adapter is not same as old one
if (oldAdapter != adapter) destroyPinnedShadow();
super.setAdapter(adapter);
}
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
if (mPinnedSection != null) {
int parentWidth = r - l - getPaddingLeft() - getPaddingRight();
int shadowWidth = mPinnedSection.view.getWidth();
if (parentWidth != shadowWidth) {
recreatePinnedShadow();
}
}
}
#Override
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
if (mPinnedSection != null) {
// prepare variables
int pLeft = getListPaddingLeft();
int pTop = getListPaddingTop();
View view = mPinnedSection.view;
// draw child
canvas.save();
int clipHeight = view.getHeight() +
(mShadowDrawable == null ? 0 : Math.min(mShadowHeight, mSectionsDistanceY));
canvas.clipRect(pLeft, pTop, pLeft + view.getWidth(), pTop + clipHeight);
canvas.translate(pLeft, pTop + mTranslateY);
drawChild(canvas, mPinnedSection.view, getDrawingTime());
if (mShadowDrawable != null && mSectionsDistanceY > 0) {
mShadowDrawable.setBounds(mPinnedSection.view.getLeft(),
mPinnedSection.view.getBottom(),
mPinnedSection.view.getRight(),
mPinnedSection.view.getBottom() + mShadowHeight);
mShadowDrawable.draw(canvas);
}
canvas.restore();
}
}
//-- touch handling methods
#Override
public boolean dispatchTouchEvent(MotionEvent ev) {
final float x = ev.getX();
final float y = ev.getY();
final int action = ev.getAction();
if (action == MotionEvent.ACTION_DOWN
&& mTouchTarget == null
&& mPinnedSection != null
&& isPinnedViewTouched(mPinnedSection.view, x, y)) { // create touch target
// user touched pinned view
mTouchTarget = mPinnedSection.view;
mTouchPoint.x = x;
mTouchPoint.y = y;
// copy down event for eventually be used later
mDownEvent = MotionEvent.obtain(ev);
}
if (mTouchTarget != null) {
if (isPinnedViewTouched(mTouchTarget, x, y)) { // forward event to pinned view
mTouchTarget.dispatchTouchEvent(ev);
}
if (action == MotionEvent.ACTION_UP) { // perform onClick on pinned view
super.dispatchTouchEvent(ev);
performPinnedItemClick();
clearTouchTarget();
} else if (action == MotionEvent.ACTION_CANCEL) { // cancel
clearTouchTarget();
} else if (action == MotionEvent.ACTION_MOVE) {
if (Math.abs(y - mTouchPoint.y) > mTouchSlop) {
// cancel sequence on touch target
MotionEvent event = MotionEvent.obtain(ev);
event.setAction(MotionEvent.ACTION_CANCEL);
mTouchTarget.dispatchTouchEvent(event);
event.recycle();
// provide correct sequence to super class for further handling
super.dispatchTouchEvent(mDownEvent);
super.dispatchTouchEvent(ev);
clearTouchTarget();
}
}
return true;
}
// call super if this was not our pinned view
return super.dispatchTouchEvent(ev);
}
private boolean isPinnedViewTouched(View view, float x, float y) {
view.getHitRect(mTouchRect);
// by taping top or bottom padding, the list performs on click on a border item.
// we don't add top padding here to keep behavior consistent.
mTouchRect.top += mTranslateY;
mTouchRect.bottom += mTranslateY + getPaddingTop();
mTouchRect.left += getPaddingLeft();
mTouchRect.right -= getPaddingRight();
return mTouchRect.contains((int)x, (int)y);
}
private void clearTouchTarget() {
mTouchTarget = null;
if (mDownEvent != null) {
mDownEvent.recycle();
mDownEvent = null;
}
}
private boolean performPinnedItemClick() {
if (mPinnedSection == null) return false;
OnItemClickListener listener = getOnItemClickListener();
if (listener != null && getAdapter().isEnabled(mPinnedSection.position)) {
View view = mPinnedSection.view;
playSoundEffect(SoundEffectConstants.CLICK);
if (view != null) {
view.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
}
listener.onItemClick(this, view, mPinnedSection.position, mPinnedSection.id);
return true;
}
return false;
}
public static boolean isItemViewTypePinned(ListAdapter adapter, int viewType) {
if (adapter instanceof HeaderViewListAdapter) {
adapter = ((HeaderViewListAdapter)adapter).getWrappedAdapter();
}
return ((PinnedSectionListAdapter) adapter).isItemViewTypePinned(viewType);
}
}
For second time get me error here :
SimpleAdapter adapt = new SimpleAdapter(_context, result, resultT, count, android.R.layout.simple_list_item_1, android.R.id.text1);
setListAdapter(adapt);
Your not using PinnedSectionListActivity current reference properly,If you wan to access your current public function inside any other class then you have to pass your current activity reference :
Declaration
private Context context;
Get current activity reference
public PinnedSectionListView(Context context, AttributeSet attrs) {
super(context, attrs);
this.context = context;
initView();
}
public PinnedSectionListView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
this.context = context;
initView();
}
Use current activity reference in onScroll() :
#Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
if (mDelegateOnScrollListener != null) { // delegate
mDelegateOnScrollListener.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount);
}
if(firstVisibleItem+visibleItemCount == totalItemCount && totalItemCount!=0)
{
if(context!=null){
((PinnedSectionListActivity)context).cleans();
}
}else {
// get expected adapter or fail fast
ListAdapter adapter = getAdapter();
if (adapter == null || visibleItemCount == 0) return; // nothing to do
final boolean isFirstVisibleItemSection = isItemViewTypePinned(adapter, adapter.getItemViewType(firstVisibleItem));
if (isFirstVisibleItemSection) {
View sectionView = getChildAt(0);
if (sectionView.getTop() == getPaddingTop()) { // view sticks to the top, no need for pinned shadow
destroyPinnedShadow();
} else { // section doesn't stick to the top, make sure we have a pinned shadow
ensureShadowForPosition(firstVisibleItem, firstVisibleItem, visibleItemCount);
}
} else { // section is not at the first visible position
int sectionPosition = findCurrentSectionPosition(firstVisibleItem);
if (sectionPosition > -1) { // we have section position
ensureShadowForPosition(sectionPosition, firstVisibleItem, visibleItemCount);
} else { // there is no section for the first visible item, destroy shadow
destroyPinnedShadow();
}
}
}
};
PinnedSectionListView full activity code :
public class PinnedSectionListView extends ListView {
//-- inner classes
/** List adapter to be implemented for being used with PinnedSectionListView adapter. */
public static interface PinnedSectionListAdapter extends ListAdapter {
/** This method shall return 'true' if views of given type has to be pinned. */
boolean isItemViewTypePinned(int viewType);
}
/** Wrapper class for pinned section view and its position in the list. */
static class PinnedSection {
public View view;
public int position;
public long id;
}
//-- class fields
// fields used for handling touch events
private final Rect mTouchRect = new Rect();
private final PointF mTouchPoint = new PointF();
private int mTouchSlop;
private View mTouchTarget;
private MotionEvent mDownEvent;
// fields used for drawing shadow under a pinned section
private GradientDrawable mShadowDrawable;
private int mSectionsDistanceY;
private int mShadowHeight;
/** Delegating listener, can be null. */
OnScrollListener mDelegateOnScrollListener;
/** Shadow for being recycled, can be null. */
PinnedSection mRecycleSection;
/** shadow instance with a pinned view, can be null. */
PinnedSection mPinnedSection;
/** Pinned view Y-translation. We use it to stick pinned view to the next section. */
int mTranslateY;
private Context context;
/** Scroll listener which does the magic */
private final OnScrollListener mOnScrollListener = new OnScrollListener() {
#Override public void onScrollStateChanged(AbsListView view, int scrollState) {
if (mDelegateOnScrollListener != null) { // delegate
mDelegateOnScrollListener.onScrollStateChanged(view, scrollState);
}
}
#Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
if (mDelegateOnScrollListener != null) { // delegate
mDelegateOnScrollListener.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount);
}
if(firstVisibleItem+visibleItemCount == totalItemCount && totalItemCount!=0)
{
if(context!=null){
((PinnedSectionListActivity)context).cleans();
}
}else {
// get expected adapter or fail fast
ListAdapter adapter = getAdapter();
if (adapter == null || visibleItemCount == 0) return; // nothing to do
final boolean isFirstVisibleItemSection = isItemViewTypePinned(adapter, adapter.getItemViewType(firstVisibleItem));
if (isFirstVisibleItemSection) {
View sectionView = getChildAt(0);
if (sectionView.getTop() == getPaddingTop()) { // view sticks to the top, no need for pinned shadow
destroyPinnedShadow();
} else { // section doesn't stick to the top, make sure we have a pinned shadow
ensureShadowForPosition(firstVisibleItem, firstVisibleItem, visibleItemCount);
}
} else { // section is not at the first visible position
int sectionPosition = findCurrentSectionPosition(firstVisibleItem);
if (sectionPosition > -1) { // we have section position
ensureShadowForPosition(sectionPosition, firstVisibleItem, visibleItemCount);
} else { // there is no section for the first visible item, destroy shadow
destroyPinnedShadow();
}
}
}
};
};
/** Default change observer. */
private final DataSetObserver mDataSetObserver = new DataSetObserver() {
#Override public void onChanged() {
recreatePinnedShadow();
};
#Override public void onInvalidated() {
recreatePinnedShadow();
}
};
//-- constructors
public PinnedSectionListView(Context context, AttributeSet attrs) {
super(context, attrs);
this.context = context;
initView();
}
public PinnedSectionListView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
this.context = context;
initView();
}
private void initView() {
setOnScrollListener(mOnScrollListener);
mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
initShadow(true);
}
//-- public API methods
public void setShadowVisible(boolean visible) {
initShadow(visible);
if (mPinnedSection != null) {
View v = mPinnedSection.view;
invalidate(v.getLeft(), v.getTop(), v.getRight(), v.getBottom() + mShadowHeight);
}
}
//-- pinned section drawing methods
public void initShadow(boolean visible) {
if (visible) {
if (mShadowDrawable == null) {
mShadowDrawable = new GradientDrawable(Orientation.TOP_BOTTOM,
new int[] { Color.parseColor("#ffa0a0a0"), Color.parseColor("#50a0a0a0"), Color.parseColor("#00a0a0a0")});
mShadowHeight = (int) (8 * getResources().getDisplayMetrics().density);
}
} else {
if (mShadowDrawable != null) {
mShadowDrawable = null;
mShadowHeight = 0;
}
}
}
/** Create shadow wrapper with a pinned view for a view at given position */
void createPinnedShadow(int position) {
// try to recycle shadow
PinnedSection pinnedShadow = mRecycleSection;
mRecycleSection = null;
// create new shadow, if needed
if (pinnedShadow == null) pinnedShadow = new PinnedSection();
// request new view using recycled view, if such
View pinnedView = getAdapter().getView(position, pinnedShadow.view, PinnedSectionListView.this);
// read layout parameters
LayoutParams layoutParams = (LayoutParams) pinnedView.getLayoutParams();
if (layoutParams == null) {
layoutParams = (LayoutParams) generateDefaultLayoutParams();
pinnedView.setLayoutParams(layoutParams);
}
int heightMode = MeasureSpec.getMode(layoutParams.height);
int heightSize = MeasureSpec.getSize(layoutParams.height);
if (heightMode == MeasureSpec.UNSPECIFIED) heightMode = MeasureSpec.EXACTLY;
int maxHeight = getHeight() - getListPaddingTop() - getListPaddingBottom();
if (heightSize > maxHeight) heightSize = maxHeight;
// measure & layout
int ws = MeasureSpec.makeMeasureSpec(getWidth() - getListPaddingLeft() - getListPaddingRight(), MeasureSpec.EXACTLY);
int hs = MeasureSpec.makeMeasureSpec(heightSize, heightMode);
pinnedView.measure(ws, hs);
pinnedView.layout(0, 0, pinnedView.getMeasuredWidth(), pinnedView.getMeasuredHeight());
mTranslateY = 0;
// initialize pinned shadow
pinnedShadow.view = pinnedView;
pinnedShadow.position = position;
pinnedShadow.id = getAdapter().getItemId(position);
// store pinned shadow
mPinnedSection = pinnedShadow;
}
/** Destroy shadow wrapper for currently pinned view */
void destroyPinnedShadow() {
if (mPinnedSection != null) {
// keep shadow for being recycled later
mRecycleSection = mPinnedSection;
mPinnedSection = null;
}
}
/** Makes sure we have an actual pinned shadow for given position. */
void ensureShadowForPosition(int sectionPosition, int firstVisibleItem, int visibleItemCount) {
if (visibleItemCount < 2) { // no need for creating shadow at all, we have a single visible item
destroyPinnedShadow();
return;
}
if (mPinnedSection != null
&& mPinnedSection.position != sectionPosition) { // invalidate shadow, if required
destroyPinnedShadow();
}
if (mPinnedSection == null) { // create shadow, if empty
createPinnedShadow(sectionPosition);
}
// align shadow according to next section position, if needed
int nextPosition = sectionPosition + 1;
if (nextPosition < getCount()) {
int nextSectionPosition = findFirstVisibleSectionPosition(nextPosition,
visibleItemCount - (nextPosition - firstVisibleItem));
if (nextSectionPosition > -1) {
View nextSectionView = getChildAt(nextSectionPosition - firstVisibleItem);
final int bottom = mPinnedSection.view.getBottom() + getPaddingTop();
mSectionsDistanceY = nextSectionView.getTop() - bottom;
if (mSectionsDistanceY < 0) {
// next section overlaps pinned shadow, move it up
mTranslateY = mSectionsDistanceY;
} else {
// next section does not overlap with pinned, stick to top
mTranslateY = 0;
}
} else {
// no other sections are visible, stick to top
mTranslateY = 0;
mSectionsDistanceY = Integer.MAX_VALUE;
}
}
}
int findFirstVisibleSectionPosition(int firstVisibleItem, int visibleItemCount) {
ListAdapter adapter = getAdapter();
int adapterDataCount = adapter.getCount();
if (getLastVisiblePosition() >= adapterDataCount) return -1; // dataset has changed, no candidate
if (firstVisibleItem+visibleItemCount >= adapterDataCount){//added to prevent index Outofbound (in case)
visibleItemCount = adapterDataCount-firstVisibleItem;
}
for (int childIndex = 0; childIndex < visibleItemCount; childIndex++) {
int position = firstVisibleItem + childIndex;
int viewType = adapter.getItemViewType(position);
if (isItemViewTypePinned(adapter, viewType)) return position;
}
return -1;
}
int findCurrentSectionPosition(int fromPosition) {
ListAdapter adapter = getAdapter();
if (fromPosition >= adapter.getCount()) return -1; // dataset has changed, no candidate
if (adapter instanceof SectionIndexer) {
// try fast way by asking section indexer
SectionIndexer indexer = (SectionIndexer) adapter;
int sectionPosition = indexer.getSectionForPosition(fromPosition);
int itemPosition = indexer.getPositionForSection(sectionPosition);
int typeView = adapter.getItemViewType(itemPosition);
if (isItemViewTypePinned(adapter, typeView)) {
return itemPosition;
} // else, no luck
}
// try slow way by looking through to the next section item above
for (int position=fromPosition; position>=0; position--) {
int viewType = adapter.getItemViewType(position);
if (isItemViewTypePinned(adapter, viewType)) return position;
}
return -1; // no candidate found
}
void recreatePinnedShadow() {
destroyPinnedShadow();
ListAdapter adapter = getAdapter();
if (adapter != null && adapter.getCount() > 0) {
int firstVisiblePosition = getFirstVisiblePosition();
int sectionPosition = findCurrentSectionPosition(firstVisiblePosition);
if (sectionPosition == -1) return; // no views to pin, exit
ensureShadowForPosition(sectionPosition,
firstVisiblePosition, getLastVisiblePosition() - firstVisiblePosition);
}
}
#Override
public void setOnScrollListener(OnScrollListener listener) {
if (listener == mOnScrollListener) {
super.setOnScrollListener(listener);
} else {
mDelegateOnScrollListener = listener;
}
}
#Override
public void onRestoreInstanceState(Parcelable state) {
super.onRestoreInstanceState(state);
post(new Runnable() {
#Override public void run() { // restore pinned view after configuration change
recreatePinnedShadow();
}
});
}
#Override
public void setAdapter(ListAdapter adapter) {
// assert adapter in debug mode
if (BuildConfig.DEBUG && adapter != null) {
if (!(adapter instanceof PinnedSectionListAdapter))
throw new IllegalArgumentException("Does your adapter implement PinnedSectionListAdapter?");
if (adapter.getViewTypeCount() < 2)
throw new IllegalArgumentException("Does your adapter handle at least two types" +
" of views in getViewTypeCount() method: items and sections?");
}
// unregister observer at old adapter and register on new one
ListAdapter oldAdapter = getAdapter();
if (oldAdapter != null) oldAdapter.unregisterDataSetObserver(mDataSetObserver);
if (adapter != null) adapter.registerDataSetObserver(mDataSetObserver);
// destroy pinned shadow, if new adapter is not same as old one
if (oldAdapter != adapter) destroyPinnedShadow();
super.setAdapter(adapter);
}
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
if (mPinnedSection != null) {
int parentWidth = r - l - getPaddingLeft() - getPaddingRight();
int shadowWidth = mPinnedSection.view.getWidth();
if (parentWidth != shadowWidth) {
recreatePinnedShadow();
}
}
}
#Override
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
if (mPinnedSection != null) {
// prepare variables
int pLeft = getListPaddingLeft();
int pTop = getListPaddingTop();
View view = mPinnedSection.view;
// draw child
canvas.save();
int clipHeight = view.getHeight() +
(mShadowDrawable == null ? 0 : Math.min(mShadowHeight, mSectionsDistanceY));
canvas.clipRect(pLeft, pTop, pLeft + view.getWidth(), pTop + clipHeight);
canvas.translate(pLeft, pTop + mTranslateY);
drawChild(canvas, mPinnedSection.view, getDrawingTime());
if (mShadowDrawable != null && mSectionsDistanceY > 0) {
mShadowDrawable.setBounds(mPinnedSection.view.getLeft(),
mPinnedSection.view.getBottom(),
mPinnedSection.view.getRight(),
mPinnedSection.view.getBottom() + mShadowHeight);
mShadowDrawable.draw(canvas);
}
canvas.restore();
}
}
//-- touch handling methods
#Override
public boolean dispatchTouchEvent(MotionEvent ev) {
final float x = ev.getX();
final float y = ev.getY();
final int action = ev.getAction();
if (action == MotionEvent.ACTION_DOWN
&& mTouchTarget == null
&& mPinnedSection != null
&& isPinnedViewTouched(mPinnedSection.view, x, y)) { // create touch target
// user touched pinned view
mTouchTarget = mPinnedSection.view;
mTouchPoint.x = x;
mTouchPoint.y = y;
// copy down event for eventually be used later
mDownEvent = MotionEvent.obtain(ev);
}
if (mTouchTarget != null) {
if (isPinnedViewTouched(mTouchTarget, x, y)) { // forward event to pinned view
mTouchTarget.dispatchTouchEvent(ev);
}
if (action == MotionEvent.ACTION_UP) { // perform onClick on pinned view
super.dispatchTouchEvent(ev);
performPinnedItemClick();
clearTouchTarget();
} else if (action == MotionEvent.ACTION_CANCEL) { // cancel
clearTouchTarget();
} else if (action == MotionEvent.ACTION_MOVE) {
if (Math.abs(y - mTouchPoint.y) > mTouchSlop) {
// cancel sequence on touch target
MotionEvent event = MotionEvent.obtain(ev);
event.setAction(MotionEvent.ACTION_CANCEL);
mTouchTarget.dispatchTouchEvent(event);
event.recycle();
// provide correct sequence to super class for further handling
super.dispatchTouchEvent(mDownEvent);
super.dispatchTouchEvent(ev);
clearTouchTarget();
}
}
return true;
}
// call super if this was not our pinned view
return super.dispatchTouchEvent(ev);
}
private boolean isPinnedViewTouched(View view, float x, float y) {
view.getHitRect(mTouchRect);
// by taping top or bottom padding, the list performs on click on a border item.
// we don't add top padding here to keep behavior consistent.
mTouchRect.top += mTranslateY;
mTouchRect.bottom += mTranslateY + getPaddingTop();
mTouchRect.left += getPaddingLeft();
mTouchRect.right -= getPaddingRight();
return mTouchRect.contains((int)x, (int)y);
}
private void clearTouchTarget() {
mTouchTarget = null;
if (mDownEvent != null) {
mDownEvent.recycle();
mDownEvent = null;
}
}
private boolean performPinnedItemClick() {
if (mPinnedSection == null) return false;
OnItemClickListener listener = getOnItemClickListener();
if (listener != null && getAdapter().isEnabled(mPinnedSection.position)) {
View view = mPinnedSection.view;
playSoundEffect(SoundEffectConstants.CLICK);
if (view != null) {
view.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
}
listener.onItemClick(this, view, mPinnedSection.position, mPinnedSection.id);
return true;
}
return false;
}
public static boolean isItemViewTypePinned(ListAdapter adapter, int viewType) {
if (adapter instanceof HeaderViewListAdapter) {
adapter = ((HeaderViewListAdapter)adapter).getWrappedAdapter();
}
return ((PinnedSectionListAdapter) adapter).isItemViewTypePinned(viewType);
}
}
hey i tried the kikose/swipeable-cards library but its performance isnt good(lags even on nexus 5 ). i tried to change the mMaxVisible in the cardContainer class from 3 to 2 the performance improved a little but still lags so i changes it to 1 and the performance looked very card on the first. after i swiped the first card the second isnt swipeable . the mTopCard in the cardcontainer class is null . how can i fix this or improve performance in others way maybe ?
here is the cardcontainer class :
public class CardContainer extends AdapterView<ListAdapter> {
public static final int INVALID_POINTER_ID = -1;
private int mActivePointerId = INVALID_POINTER_ID;
private static final double DISORDERED_MAX_ROTATION_RADIANS = Math.PI / 64;
private int mNumberOfCards = -1;
Context context;
private final DataSetObserver mDataSetObserver = new DataSetObserver() {
#Override
public void onChanged() {
super.onChanged();
clearStack();
ensureFull();
}
#Override
public void onInvalidated() {
super.onInvalidated();
clearStack();
}
};
private final Random mRandom = new Random();
private final Rect boundsRect = new Rect();
private final Rect childRect = new Rect();
private final Matrix mMatrix = new Matrix();
//TODO: determine max dynamically based on device speed
private int mMaxVisible =2;
private GestureDetector mGestureDetector;
private int mFlingSlop;
private Orientation mOrientation;
private ListAdapter mListAdapter;
private float mLastTouchX;
private float mLastTouchY;
private View mTopCard;
private int mTouchSlop;
private int mGravity;
private int mNextAdapterPosition;
private boolean mDragging;
private boolean animating;
int index;
Profile cardModel;
public CardContainer(Context context) {
super(context);
this.context = context;
setOrientation(Orientation.Disordered);
setGravity(Gravity.TOP);
init();
animating = false;
}
public CardContainer(Context context, AttributeSet attrs) {
super(context, attrs);
this.context = context;
initFromXml(attrs);
init();
}
public CardContainer(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
this.context = context;
initFromXml(attrs);
init();
}
private void init() {
ViewConfiguration viewConfiguration = ViewConfiguration
.get(getContext());
mFlingSlop = viewConfiguration.getScaledMinimumFlingVelocity();
mTouchSlop = viewConfiguration.getScaledTouchSlop();
mGestureDetector = new GestureDetector(getContext(),
new GestureListener());
}
private void initFromXml(AttributeSet attr) {
TypedArray a = getContext().obtainStyledAttributes(attr,
R.styleable.CardContainer);
setGravity(a.getInteger(R.styleable.CardContainer_android_gravity,
Gravity.TOP));
int orientation = a
.getInteger(R.styleable.CardContainer_orientation, 1);
setOrientation(Orientation.fromIndex(orientation));
index = 0;
a.recycle();
}
#Override
public ListAdapter getAdapter() {
return mListAdapter;
}
#Override
public void setAdapter(ListAdapter adapter) {
index = 0;
if (mListAdapter != null)
mListAdapter.unregisterDataSetObserver(mDataSetObserver);
clearStack();
mTopCard = null;
mListAdapter = adapter;
mNextAdapterPosition = 0;
adapter.registerDataSetObserver(mDataSetObserver);
ensureFull();
Log.e("GETCHILD CARD CONT","getChildAt(getChildCount() - 1)==================="+getChildAt(getChildCount() - 1));
Log.e("GETCHILD CARD CONT","getChildCount()==================="+getChildCount());
if (getChildCount() != 0) {
mTopCard = getChildAt(getChildCount() - 1);
mTopCard.setLayerType(LAYER_TYPE_HARDWARE, null);
}
mNumberOfCards = getAdapter().getCount();
requestLayout();
((Profile)adapter.getItem(0)).getOnCardDimissedListener().initialiseCard();
}
private void ensureFull() {
while (mNextAdapterPosition < mListAdapter.getCount() && getChildCount() < mMaxVisible) {
View view = mListAdapter.getView(mNextAdapterPosition, null, this);
view.setLayerType(LAYER_TYPE_SOFTWARE, null);
if(mOrientation == Orientation.Disordered) {
view.setRotation(getDisorderedRotation());
}
addViewInLayout(view, 0, new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT,
mListAdapter.getItemViewType(mNextAdapterPosition)), false);
requestLayout();
mNextAdapterPosition += 1;
}
}
private void clearStack() {
removeAllViewsInLayout();
mNextAdapterPosition = 0;
mTopCard = null;
}
public Orientation getOrientation() {
return mOrientation;
}
public void setOrientation(Orientation orientation) {
if (orientation == null)
throw new NullPointerException("Orientation may not be null");
if(mOrientation != orientation) {
this.mOrientation = orientation;
if(orientation == Orientation.Disordered) {
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
child.setRotation(getDisorderedRotation());
}
}
else {
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
child.setRotation(0);
}
}
requestLayout();
}
}
private float getDisorderedRotation() {
return (float) Math.toDegrees(mRandom.nextGaussian() * DISORDERED_MAX_ROTATION_RADIANS);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int requestedWidth = getMeasuredWidth() - getPaddingLeft() - getPaddingRight();
int requestedHeight = getMeasuredHeight() - getPaddingTop() - getPaddingBottom();
int childWidth, childHeight;
if (mOrientation == Orientation.Disordered) {
int R1, R2;
if (requestedWidth >= requestedHeight) {
R1 = requestedHeight;
R2 = requestedWidth;
} else {
R1 = requestedWidth;
R2 = requestedHeight;
}
childWidth = (int) ((R1 * Math.cos(DISORDERED_MAX_ROTATION_RADIANS) - R2 * Math.sin(DISORDERED_MAX_ROTATION_RADIANS)) / Math.cos(2 * DISORDERED_MAX_ROTATION_RADIANS));
childHeight = (int) ((R2 * Math.cos(DISORDERED_MAX_ROTATION_RADIANS) - R1 * Math.sin(DISORDERED_MAX_ROTATION_RADIANS)) / Math.cos(2 * DISORDERED_MAX_ROTATION_RADIANS));
} else {
childWidth = requestedWidth;
childHeight = requestedHeight;
}
int childWidthMeasureSpec, childHeightMeasureSpec;
childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(childWidth, MeasureSpec.AT_MOST);
childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.AT_MOST);
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
assert child != null;
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
}
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
for (int i = 0; i < getChildCount(); i++) {
boundsRect.set(0, 0, getWidth(), getHeight());
View view = getChildAt(i);
int w, h;
w = view.getMeasuredWidth();
h = view.getMeasuredHeight();
Gravity.apply(mGravity, w, h, boundsRect, childRect);
view.layout(childRect.left, childRect.top, childRect.right, childRect.bottom);
}
}
int holdingCard;
View likeImage, dislikeImage;
#Override
public boolean onTouchEvent(MotionEvent event) {
if (mTopCard == null || animating) {
return false;
}
if (mGestureDetector.onTouchEvent(event)) {
return true;
}
final int pointerIndex;
final float x, y;
final float dx, dy;
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
getHitRect(mTopCard,childRect);
pointerIndex = event.getActionIndex();
x = event.getX(pointerIndex);
y = event.getY(pointerIndex);
if (!childRect.contains((int) x, (int) y)) {
return false;
}
mLastTouchX = x;
mLastTouchY = y;
mActivePointerId = event.getPointerId(pointerIndex);
float[] points = new float[]{x - mTopCard.getLeft(), y - mTopCard.getTop()};
mTopCard.getMatrix().invert(mMatrix);
mMatrix.mapPoints(points);
mTopCard.setPivotX(points[0]);
mTopCard.setPivotY(points[1]);
break;
case MotionEvent.ACTION_MOVE:
pointerIndex = event.findPointerIndex(mActivePointerId);
x = event.getX(pointerIndex);
y = event.getY(pointerIndex);
dx = x - mLastTouchX;
dy = y - mLastTouchY;
Profile profile = (Profile) getAdapter().getItem(0);
likeImage = mTopCard.findViewById(R.id.like_text_image);
dislikeImage = mTopCard.findViewById(R.id.dislike_text_image);
if (mTopCard.getTranslationX() + dx > 30) {
profile.getOnCardDimissedListener().tendToLike();
dislikeImage.setVisibility(View.INVISIBLE);
likeImage.setVisibility(View.VISIBLE);
}
else if (mTopCard.getTranslationX() + dx < 30) {
profile.getOnCardDimissedListener().tendToDislike();
likeImage.setVisibility(View.INVISIBLE);
dislikeImage.setVisibility(View.VISIBLE);
} else {
likeImage.setVisibility(View.INVISIBLE);
dislikeImage.setVisibility(View.INVISIBLE);
}
if (Math.abs(dx) > mTouchSlop || Math.abs(dy) > mTouchSlop) {
mDragging = true;
}
if(!mDragging) {
return true;
}
mTopCard.setTranslationX(mTopCard.getTranslationX() + dx);
mTopCard.setTranslationY(mTopCard.getTranslationY() + dy);
mTopCard.setRotation(40 * mTopCard.getTranslationX() / (getWidth() / 2.f));
mLastTouchX = x;
mLastTouchY = y;
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
if (!mDragging) {
return true;
}
mDragging = false;
mActivePointerId = INVALID_POINTER_ID;
ValueAnimator animator = ObjectAnimator.ofPropertyValuesHolder(mTopCard,
PropertyValuesHolder.ofFloat("translationX", 0),
PropertyValuesHolder.ofFloat("translationY", 0),
PropertyValuesHolder.ofFloat("rotation", (float) Math.toDegrees(mRandom.nextGaussian() * DISORDERED_MAX_ROTATION_RADIANS)),
PropertyValuesHolder.ofFloat("pivotX", mTopCard.getWidth() / 2.f),
PropertyValuesHolder.ofFloat("pivotY", mTopCard.getHeight() / 2.f)
).setDuration(250);
animator.setInterpolator(new AccelerateInterpolator());
animator.addListener(new AnimatorListener() {
#Override
public void onAnimationStart(Animator animation) {
animating = true;
}
#Override
public void onAnimationRepeat(Animator animation) {
animating = true;
}
#Override
public void onAnimationEnd(Animator animation) {
animating = false;
dislikeImage.setVisibility(View.INVISIBLE);
likeImage.setVisibility(View.INVISIBLE);
((Profile) getAdapter().getItem(0)).getOnCardDimissedListener().cancelCard();
}
#Override
public void onAnimationCancel(Animator animation) {
animating = false;
}
});
animator.start();
break;
case MotionEvent.ACTION_POINTER_UP:
pointerIndex = event.getActionIndex();
final int pointerId = event.getPointerId(pointerIndex);
if (pointerId == mActivePointerId) {
final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
mLastTouchX = event.getX(newPointerIndex);
mLastTouchY = event.getY(newPointerIndex);
mActivePointerId = event.getPointerId(newPointerIndex);
}
break;
}
return true;
}
private void getHitRect(View v, Rect rect) {
rect.left = (int) (v.getLeft() + v.getTranslationX());
rect.top = (int) (v.getTop() + v.getTranslationY());
rect.right = rect.left + v.getWidth();
rect.bottom = rect.top + v.getHeight();
}
public boolean isAnimating()
{
return animating;
}
public Profile getCurrentCard() {
if(index + 1 > getAdapter().getCount())
return null;
Profile profile = (Profile) getAdapter().getItem(index);
if (index + 1 == getAdapter().getCount())
profile.setLast(true);
return profile;
}
#Override
public boolean onInterceptTouchEvent(MotionEvent event) {
try {
Log.e("CARD CONTAINER","MTOPCARD =====================+++++++++ "+mTopCard);
if (mTopCard == null) {
return false;
}
if (mGestureDetector.onTouchEvent(event)) {
return true;
}
final int pointerIndex;
final float x, y;
final float dx, dy;
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
getHitRect(mTopCard,childRect);
Profile Profile = (Profile) getAdapter().getItem(0);
if (Profile.getOnClickListener() != null) {
Profile.getOnClickListener().OnClickListener();
}
pointerIndex = event.getActionIndex();
x = event.getX(pointerIndex);
y = event.getY(pointerIndex);
if (!childRect.contains((int) x, (int) y)) {
return false;
}
mLastTouchX = x;
mLastTouchY = y;
mActivePointerId = event.getPointerId(pointerIndex);
break;
case MotionEvent.ACTION_MOVE:
pointerIndex = event.findPointerIndex(mActivePointerId);
x = event.getX(pointerIndex);
y = event.getY(pointerIndex);
if (Math.abs(x - mLastTouchX) > mTouchSlop || Math.abs(y - mLastTouchY) > mTouchSlop) {
float[] points = new float[]{x - mTopCard.getLeft(), y - mTopCard.getTop()};
mTopCard.getMatrix().invert(mMatrix);
mMatrix.mapPoints(points);
mTopCard.setPivotX(points[0]);
mTopCard.setPivotY(points[1]);
return true;
}
}
return false;
} catch (Exception e) {
// TODO Auto-generated catch block
return super.onInterceptTouchEvent(event);
}
}
#Override
public View getSelectedView() {
throw new UnsupportedOperationException();
}
#Override
public void setSelection(int position) {
throw new UnsupportedOperationException();
}
public int getGravity() {
return mGravity;
}
public void setGravity(int gravity) {
mGravity = gravity;
}
public static class LayoutParams extends ViewGroup.LayoutParams {
int viewType;
public LayoutParams(Context c, AttributeSet attrs) {
super(c, attrs);
}
public LayoutParams(int width, int height) {
super(width, height);
}
public LayoutParams(ViewGroup.LayoutParams source) {
super(source);
}
public LayoutParams(int w, int h, int viewType) {
super(w, h);
this.viewType = viewType;
}
}
private class GestureListener extends SimpleOnGestureListener {
#Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
final View topCard = mTopCard;
float dx = e2.getX() - e1.getX();
if (Math.abs(dx) > mTouchSlop &&
Math.abs(velocityX) > Math.abs(velocityY) &&
Math.abs(velocityX) > mFlingSlop * 3) {
float targetX = topCard.getX();
float targetY = topCard.getY();
long duration = 0;
boundsRect.set(0 - topCard.getWidth() - 100, 0 - topCard.getHeight() - 100, getWidth() + 100, getHeight() + 100);
while (boundsRect.contains((int) targetX, (int) targetY)) {
targetX += velocityX / 10;
targetY += velocityY / 10;
duration += 100;
}
duration = Math.min(400, duration);
cardModel = (Profile) getAdapter().getItem(0);
if (cardModel.getOnCardDimissedListener() != null) {
if ( targetX > 0 ) {
{
if(MyUser.getInstance((Activity)context).getVip().equals("1") || (Float.valueOf((getCurrentCard()).getScore())>MyGenericApp.getInstance((Activity)context).getMinScoreMatch()))
{
mTopCard = getChildAt(getChildCount() - 2);
if (mTopCard != null)
mTopCard.setLayerType(LAYER_TYPE_HARDWARE, null);
cardModel.getOnCardDimissedListener().onLike();
}
else
{
cardModel.getOnCardDimissedListener().forcedToDislike();
return super.onFling(e1, e2, velocityX, velocityY);
}
}
} else {
mTopCard = getChildAt(getChildCount() - 2);
if (mTopCard != null)
mTopCard.setLayerType(LAYER_TYPE_HARDWARE, null);
cardModel.getOnCardDimissedListener().onDislike();
}
index++;
}
animating = true;
topCard.animate()
.setDuration(duration)
.alpha(.75f)
.setInterpolator(new LinearInterpolator())
.x(targetX)
.y(targetY)
.rotation(Math.copySign(45, velocityX))
.setListener(new AnimatorListenerAdapter() {
#Override
public void onAnimationEnd(Animator animation) {
removeViewInLayout(topCard);
ensureFull();
if(getCurrentCard()!=null)
cardModel.getOnCardDimissedListener().initialiseCard();
animating = false;
}
#Override
public void onAnimationCancel(Animator animation) {
onAnimationEnd(animation);
}
});
return true;
} else
return false;
}
}
public void dislike(){
final View topCard = mTopCard;
View likeImage = mTopCard.findViewById(R.id.like_text_image);
View dislikeImage = mTopCard.findViewById(R.id.dislike_text_image);
dislikeImage.setVisibility(View.VISIBLE);
likeImage.setVisibility(View.INVISIBLE);
mTopCard = getChildAt(getChildCount() - 2);
Profile cardModel = (Profile)getAdapter().getItem(0);
if(mTopCard != null)
mTopCard.setLayerType(LAYER_TYPE_HARDWARE, null);
animating = true;
topCard.animate()
.setDuration(400)
.alpha(.75f)
.setInterpolator(new LinearInterpolator())
.x(-GlobalFunctions.getScreenWidth(context))
.y(topCard.getY())
.rotation(-45)
.setListener(new AnimatorListenerAdapter() {
#Override
public void onAnimationEnd(Animator animation) {
removeViewInLayout(topCard);
ensureFull();
animating = false;
}
#Override
public void onAnimationCancel(Animator animation) {
onAnimationEnd(animation);
}
});
if(cardModel.getOnCardDimissedListener() != null){
cardModel.getOnCardDimissedListener().onDislike();
}
index++;
if(index < getAdapter().getCount())
((Profile)getAdapter().getItem(0)).getOnCardDimissedListener().initialiseCard();
}
public void like(){
final View topCard = mTopCard;
View likeImage = mTopCard.findViewById(R.id.like_text_image);
View dislikeImage = mTopCard.findViewById(R.id.dislike_text_image);
likeImage.setVisibility(View.VISIBLE);
dislikeImage.setVisibility(View.INVISIBLE);
mTopCard = getChildAt(getChildCount() - 2);
Profile cardModel = (Profile)getAdapter().getItem(0);
if(mTopCard != null)
mTopCard.setLayerType(LAYER_TYPE_HARDWARE, null);
animating = true;
topCard.animate()
.setDuration(400)
.alpha(.75f)
.setInterpolator(new LinearInterpolator())
.x(GlobalFunctions.getScreenWidth(context))
.y(topCard.getY())
.rotation(45)
.setListener(new AnimatorListenerAdapter() {
#Override
public void onAnimationEnd(Animator animation) {
removeViewInLayout(topCard);
ensureFull();
animating = false;
}
#Override
public void onAnimationCancel(Animator animation) {
onAnimationEnd(animation);
}
});
if(cardModel.getOnCardDimissedListener() != null){
cardModel.getOnCardDimissedListener().onLike();
}
index++;
if(index < getAdapter().getCount())
((Profile)getAdapter().getItem(0)).getOnCardDimissedListener().initialiseCard();
}
}
I have a custom listview which is having a custom inexscroller which draws itself when you scroll through the list. Now that doesnt look nice when my listview is not having too many items. So what I want to do is to hide the IndexScroller when the items are less than a particular number to be scrollable. I have done everything but I am not able to hide the list view. Please help:
Here are the classes used:
IndexableListViewActivity
public class IndexableListViewActivity extends Activity implements OnClickListener
{
private ArrayList<String> mItems;
private IndexableListView mListView;
TextView MyTasks, TeamTasks, username, fullusername;
RelativeLayout listlay;
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mItems = new ArrayList<String>();
mItems.add("Diary of a Wimpy Kid 6: Cabin Fever");
mItems.add("Steve Jobs");
mItems.add("Inheritance (The Inheritance Cycle)");
mItems.add("11/22/63: A Novel");
mItems.add("The Hunger Games");
mItems.add("The LEGO Ideas Book");
mItems.add("Explosive Eighteen: A Stephanie Plum Novel");
mItems.add("Catching Fire (The Second Book of the Hunger Games)");
mItems.add("Elder Scrolls V: Skyrim: Prima Official Game Guide");
mItems.add("Death Comes to Pemberley");
mItems.add("Diary of a Wimpy Kid 6: Cabin Fever");
mItems.add("Steve Jobs");
mItems.add("Inheritance (The Inheritance Cycle)");
mItems.add("11/22/63: A Novel");
mItems.add("The Hunger Games");
mItems.add("The LEGO Ideas Book");
mItems.add("Explosive Eighteen: A Stephanie Plum Novel");
mItems.add("Catching Fire (The Second Book of the Hunger Games)");
mItems.add("Elder Scrolls V: Skyrim: Prima Official Game Guide");
mItems.add("Death Comes to Pemberley");
mItems.add("Make this list longer");
mItems.add("A");
mItems.add("B");
// mItems.add("C");
// mItems.add("D");
// mItems.add("E");
// mItems.add("F");
// mItems.add("H");
// mItems.add("I");
// mItems.add("J");
// mItems.add("K");
// mItems.add("L");
// mItems.add("M");
// mItems.add("N");
// mItems.add("O");
// mItems.add("P");
// mItems.add("Q");
// mItems.add("R");
// mItems.add("S");
// mItems.add("T");
// mItems.add("U");
// mItems.add("V");
// mItems.add("W");
// mItems.add("X");
// mItems.add("Y");
// mItems.add("Z");
Collections.sort(mItems);
ContentAdapter adapter = new ContentAdapter(this,
android.R.layout.simple_list_item_1, mItems);
mListView = (IndexableListView) findViewById(R.id.listview);
mListView.setAdapter(adapter);
mListView.setFastScrollEnabled(true);
MyTasks = (TextView)findViewById(R.id.myTasks);
MyTasks.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// TODO Auto-generated method stub
MyTasks.setBackgroundResource(R.drawable.rectangle_selected);
TeamTasks.setBackgroundResource(R.drawable.rectangle);
if(mListView.getLastVisiblePosition() + 1 == mListView.getCount()) {
Toast.makeText(getBaseContext(), "No need to scroll", Toast.LENGTH_SHORT).show();
}
else
{
Toast.makeText(getBaseContext(), "Need to scroll", Toast.LENGTH_SHORT).show();
}
}
});
TeamTasks = (TextView)findViewById(R.id.teamTasks);
TeamTasks.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// TODO Auto-generated method stub
TeamTasks.setBackgroundResource(R.drawable.rectangle_selected);
MyTasks.setBackgroundResource(R.drawable.rectangle);
}
});
}
private class ContentAdapter extends ArrayAdapter<String> implements SectionIndexer {
private String mSections = "#ABCDEFGHIJKLMNOPQRSTUVWXYZ";
public ContentAdapter(Context context, int textViewResourceId,
List<String> objects) {
super(context, textViewResourceId, objects);
}
#Override
public int getPositionForSection(int section) {
// If there is no item for current section, previous section will be selected
for (int i = section; i >= 0; i--) {
for (int j = 0; j < getCount(); j++) {
if (i == 0) {
// For numeric section
for (int k = 0; k <= 9; k++) {
if (StringMatcher.match(String.valueOf(getItem(j).charAt(0)), String.valueOf(k)))
return j;
}
} else {
if (StringMatcher.match(String.valueOf(getItem(j).charAt(0)), String.valueOf(mSections.charAt(i))))
return j;
}
}
}
return 0;
}
#Override
public int getSectionForPosition(int position) {
return 0;
}
#Override
public Object[] getSections() {
String[] sections = new String[mSections.length()];
for (int i = 0; i < mSections.length(); i++)
sections[i] = String.valueOf(mSections.charAt(i));
return sections;
}
}
#Override
public void onClick(View v) {
// TODO Auto-generated method stub
}
}
StrngMatcher.java
public class StringMatcher {
public static boolean match(String value, String keyword) {
if (value == null || keyword == null)
return false;
if (keyword.length() > value.length())
return false;
int i = 0, j = 0;
do {
int vi = value.charAt(i);
int kj = keyword.charAt(j);
if (isKorean(vi) && isInitialSound(kj)) {
} else {
if (vi == kj) {
i++;
j++;
} else if (j > 0)
break;
else
i++;
}
} while (i < value.length() && j < keyword.length());
return (j == keyword.length())? true : false;
}
private static boolean isKorean(int i) {
return false;
}
private static boolean isInitialSound(int i) {
return false;
}
}
IndexableListView.java
public class IndexableListView extends ListView {
private boolean mIsFastScrollEnabled = false;
private IndexScroller mScroller = null;
private GestureDetector mGestureDetector = null;
public IndexableListView(Context context) {
super(context);
}
public IndexableListView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public IndexableListView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
#Override
public boolean isFastScrollEnabled() {
return mIsFastScrollEnabled;
}
#Override
public void setFastScrollEnabled(boolean enabled) {
mIsFastScrollEnabled = enabled;
if (mIsFastScrollEnabled) {
if (mScroller == null)
mScroller = new IndexScroller(getContext(), this);
}
else {
if (mScroller != null) {
mScroller.hide();
mScroller = null;
}
}
}
#Override
public void draw(Canvas canvas) {
super.draw(canvas);
// Overlay index bar
if (mScroller != null)
mScroller.draw(canvas);
}
#Override
public boolean onTouchEvent(MotionEvent ev) {
// Intercept ListView's touch event
if (mScroller != null && mScroller.onTouchEvent(ev))
return true;
if (mGestureDetector == null) {
mGestureDetector = new GestureDetector(getContext(), new GestureDetector.SimpleOnGestureListener() {
#Override
public boolean onFling(MotionEvent e1, MotionEvent e2,
float velocityX, float velocityY) {
// If fling happens, index bar shows
if(mScroller!=null)
mScroller.show();
return super.onFling(e1, e2, velocityX, velocityY);
}
});
}
mGestureDetector.onTouchEvent(ev);
return super.onTouchEvent(ev);
}
#Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return true;
}
#Override
public void setAdapter(ListAdapter adapter) {
super.setAdapter(adapter);
if (mScroller != null)
mScroller.setAdapter(adapter);
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
if (mScroller != null)
mScroller.onSizeChanged(w, h, oldw, oldh);
}
}
IndexScroller.java
public class IndexScroller {
private float mIndexbarWidth;
private float mIndexbarMargin;
private float mPreviewPadding;
private float mDensity;
private float mScaledDensity;
private float mAlphaRate;
private int mState = STATE_HIDDEN;
private int mListViewWidth;
private int mListViewHeight;
private int mCurrentSection = -1;
private boolean mIsIndexing = false;
private ListView mListView = null;
private SectionIndexer mIndexer = null;
private String[] mSections = null;
private RectF mIndexbarRect;
private static final int STATE_HIDDEN = 0;
private static final int STATE_SHOWING = 1;
private static final int STATE_SHOWN = 2;
private static final int STATE_HIDING = 3;
public IndexScroller(Context context, ListView lv) {
mDensity = context.getResources().getDisplayMetrics().density;
mScaledDensity = context.getResources().getDisplayMetrics().scaledDensity;
mListView = lv;
setAdapter(mListView.getAdapter());
mIndexbarWidth = 20 * mDensity;
mIndexbarMargin = 2 * mDensity;
mPreviewPadding = 5 * mDensity;
}
public void draw(Canvas canvas) {
if (mState == STATE_HIDDEN)
return;
// mAlphaRate determines the rate of opacity
Paint indexbarPaint = new Paint();
indexbarPaint.setColor(Color.BLACK);
indexbarPaint.setAlpha((int) (64 * mAlphaRate));
indexbarPaint.setAntiAlias(true);
canvas.drawRoundRect(mIndexbarRect, 5 * mDensity, 5 * mDensity, indexbarPaint);
if (mSections != null && mSections.length > 0) {
// Preview is shown when mCurrentSection is set
if (mCurrentSection >= 0) {
Paint previewPaint = new Paint();
previewPaint.setColor(Color.BLACK);
previewPaint.setAlpha(96);
previewPaint.setAntiAlias(true);
previewPaint.setShadowLayer(3, 0, 0, Color.argb(64, 0, 0, 0));
Paint previewTextPaint = new Paint();
previewTextPaint.setColor(Color.WHITE);
previewTextPaint.setAntiAlias(true);
previewTextPaint.setTextSize(50 * mScaledDensity);
float previewTextWidth = previewTextPaint.measureText(mSections[mCurrentSection]);
float previewSize = 2 * mPreviewPadding + previewTextPaint.descent() - previewTextPaint.ascent();
RectF previewRect = new RectF((mListViewWidth - previewSize) / 2
, (mListViewHeight - previewSize) / 2
, (mListViewWidth - previewSize) / 2 + previewSize
, (mListViewHeight - previewSize) / 2 + previewSize);
canvas.drawRoundRect(previewRect, 5 * mDensity, 5 * mDensity, previewPaint);
canvas.drawText(mSections[mCurrentSection], previewRect.left + (previewSize - previewTextWidth) / 2 - 1
, previewRect.top + mPreviewPadding - previewTextPaint.ascent() + 1, previewTextPaint);
}
Paint indexPaint = new Paint();
indexPaint.setColor(Color.WHITE);
indexPaint.setAlpha((int) (255 * mAlphaRate));
indexPaint.setAntiAlias(true);
indexPaint.setTextSize(12 * mScaledDensity);
float sectionHeight = (mIndexbarRect.height() - 2 * mIndexbarMargin) / mSections.length;
float paddingTop = (sectionHeight - (indexPaint.descent() - indexPaint.ascent())) / 2;
for (int i = 0; i < mSections.length; i++) {
float paddingLeft = (mIndexbarWidth - indexPaint.measureText(mSections[i])) / 2;
canvas.drawText(mSections[i], mIndexbarRect.left + paddingLeft
, mIndexbarRect.top + mIndexbarMargin + sectionHeight * i + paddingTop - indexPaint.ascent(), indexPaint);
}
}
}
public boolean onTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
// If down event occurs inside index bar region, start indexing
if (mState != STATE_HIDDEN && contains(ev.getX(), ev.getY())) {
setState(STATE_SHOWN);
// It demonstrates that the motion event started from index bar
mIsIndexing = true;
// Determine which section the point is in, and move the list to that section
mCurrentSection = getSectionByPoint(ev.getY());
mListView.setSelection(mIndexer.getPositionForSection(mCurrentSection));
return true;
}
break;
case MotionEvent.ACTION_MOVE:
if (mIsIndexing) {
// If this event moves inside index bar
if (contains(ev.getX(), ev.getY())) {
// Determine which section the point is in, and move the list to that section
mCurrentSection = getSectionByPoint(ev.getY());
mListView.setSelection(mIndexer.getPositionForSection(mCurrentSection));
}
return true;
}
break;
case MotionEvent.ACTION_UP:
if (mIsIndexing) {
mIsIndexing = false;
mCurrentSection = -1;
}
if (mState == STATE_SHOWN)
setState(STATE_HIDING);
break;
}
return false;
}
public void onSizeChanged(int w, int h, int oldw, int oldh) {
mListViewWidth = w;
mListViewHeight = h;
mIndexbarRect = new RectF(w - mIndexbarMargin - mIndexbarWidth
, mIndexbarMargin
, w - mIndexbarMargin
, h - mIndexbarMargin);
}
public void show() {
if (mState == STATE_HIDDEN)
setState(STATE_SHOWING);
else if (mState == STATE_HIDING)
setState(STATE_HIDING);
}
public void hide() {
if (mState == STATE_SHOWN)
setState(STATE_HIDING);
}
public void setAdapter(Adapter adapter) {
if (adapter instanceof SectionIndexer) {
mIndexer = (SectionIndexer) adapter;
mSections = (String[]) mIndexer.getSections();
}
}
private void setState(int state) {
if (state < STATE_HIDDEN || state > STATE_HIDING)
return;
mState = state;
switch (mState) {
case STATE_HIDDEN:
// Cancel any fade effect
mHandler.removeMessages(0);
break;
case STATE_SHOWING:
// Start to fade in
mAlphaRate = 0;
fade(0);
break;
case STATE_SHOWN:
// Cancel any fade effect
mHandler.removeMessages(0);
break;
case STATE_HIDING:
// Start to fade out after three seconds
mAlphaRate = 1;
fade(3000);
break;
}
}
private boolean contains(float x, float y) {
// Determine if the point is in index bar region, which includes the right margin of the bar
return (x >= mIndexbarRect.left && y >= mIndexbarRect.top && y <= mIndexbarRect.top + mIndexbarRect.height());
}
private int getSectionByPoint(float y) {
if (mSections == null || mSections.length == 0)
return 0;
if (y < mIndexbarRect.top + mIndexbarMargin)
return 0;
if (y >= mIndexbarRect.top + mIndexbarRect.height() - mIndexbarMargin)
return mSections.length - 1;
return (int) ((y - mIndexbarRect.top - mIndexbarMargin) / ((mIndexbarRect.height() - 2 * mIndexbarMargin) / mSections.length));
}
private void fade(long delay) {
mHandler.removeMessages(0);
mHandler.sendEmptyMessageAtTime(0, SystemClock.uptimeMillis() + delay);
}
private Handler mHandler = new Handler() {
#Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (mState) {
case STATE_SHOWING:
// Fade in effect
mAlphaRate += (1 - mAlphaRate) * 0.2;
if (mAlphaRate > 0.9) {
mAlphaRate = 1;
setState(STATE_SHOWN);
}
mListView.invalidate();
fade(10);
break;
case STATE_SHOWN:
// If no action, hide automatically
setState(STATE_HIDING);
break;
case STATE_HIDING:
// Fade out effect
mAlphaRate -= mAlphaRate * 0.2;
if (mAlphaRate < 0.1) {
mAlphaRate = 0;
setState(STATE_HIDDEN);
}
mListView.invalidate();
fade(10);
break;
}
}
};
}
In this, IndexableListView and IndexScroller are in same package and other 2 classes are in 2 different packages.
Please help how to just hide the IndexScroller so that it doesnt show up on touches.
Since no one bothered to answer, here is the thing to be done.,
For all lines where state is set as shown in IndexScroller.java, enclose them in:
isTrue = mListView.getLastVisiblePosition() + 1 == mListView.getCount();
if(!isTrue)
{
//State Shown
}