RecyclerView make item span multiple rows [duplicate] - java

I have a collection of photos, and I'm using a RecyclerView to display them. I want to have the first element in my RecyclerView span 2 columns AND 2 rows:
I know I can span 2 columns with setSpanSizeLookup:
GridLayoutManager gridLayoutManager = new GridLayoutManager(this, 3);
gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
#Override
public int getSpanSize(int position) {
if (position == 0) {
return 2;
} else {
return 1;
}
}
});
but how can I also make the first item span 2 rows as well?
I have tried setting the first item's height to be different by inflating a different layout with double the height of the others, but that resulted in every item on the same row as the first item also being stretched to that height:
#Override
public ProfilePicViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View itemView;
if (viewType == TYPE_MAIN_PHOTO) {
itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.card_view_main_profile_photo, parent, false);
} else {
itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.card_view_profile_photo, parent, false);
}
return new ProfilePicViewHolder(itemView);
}

You cannot achieve this behavior with GridLayoutManager, because it only supports spanning multiple columns.
Nick Butcher is currently implementing a custom SpannedGridLayoutManager that does exactly what you want. It allows you to span multiple rows and columns at the same time. The implementation is still WIP, but already works quite well.
SpannedGridLayoutManager.java
package io.plaidapp.ui.recyclerview;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.PointF;
import android.graphics.Rect;
import android.support.annotation.Keep;
import android.support.annotation.NonNull;
import android.support.v7.widget.LinearSmoothScroller;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.util.SparseArray;
import android.view.View;
import android.view.ViewGroup;
import java.util.ArrayList;
import java.util.List;
import io.plaidapp.R;
/**
* A {#link RecyclerView.LayoutManager} which displays a regular grid (i.e. all cells are the same
* size) and allows simultaneous row & column spanning.
*/
public class SpannedGridLayoutManager extends RecyclerView.LayoutManager {
private GridSpanLookup spanLookup;
private int columns = 1;
private float cellAspectRatio = 1f;
private int cellHeight;
private int[] cellBorders;
private int firstVisiblePosition;
private int lastVisiblePosition;
private int firstVisibleRow;
private int lastVisibleRow;
private boolean forceClearOffsets;
private SparseArray<GridCell> cells;
private List<Integer> firstChildPositionForRow; // key == row, val == first child position
private int totalRows;
private final Rect itemDecorationInsets = new Rect();
public SpannedGridLayoutManager(GridSpanLookup spanLookup, int columns, float cellAspectRatio) {
this.spanLookup = spanLookup;
this.columns = columns;
this.cellAspectRatio = cellAspectRatio;
setAutoMeasureEnabled(true);
}
#Keep /* XML constructor, see RecyclerView#createLayoutManager */
public SpannedGridLayoutManager(
Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
TypedArray a = context.obtainStyledAttributes(
attrs, R.styleable.SpannedGridLayoutManager, defStyleAttr, defStyleRes);
columns = a.getInt(R.styleable.SpannedGridLayoutManager_spanCount, 1);
parseAspectRatio(a.getString(R.styleable.SpannedGridLayoutManager_aspectRatio));
// TODO use this!
int orientation = a.getInt(
R.styleable.SpannedGridLayoutManager_android_orientation, RecyclerView.VERTICAL);
a.recycle();
setAutoMeasureEnabled(true);
}
public interface GridSpanLookup {
SpanInfo getSpanInfo(int position);
}
public void setSpanLookup(#NonNull GridSpanLookup spanLookup) {
this.spanLookup = spanLookup;
}
public static class SpanInfo {
public int columnSpan;
public int rowSpan;
public SpanInfo(int columnSpan, int rowSpan) {
this.columnSpan = columnSpan;
this.rowSpan = rowSpan;
}
public static final SpanInfo SINGLE_CELL = new SpanInfo(1, 1);
}
public static class LayoutParams extends RecyclerView.LayoutParams {
int columnSpan;
int rowSpan;
public LayoutParams(Context c, AttributeSet attrs) {
super(c, attrs);
}
public LayoutParams(int width, int height) {
super(width, height);
}
public LayoutParams(ViewGroup.MarginLayoutParams source) {
super(source);
}
public LayoutParams(ViewGroup.LayoutParams source) {
super(source);
}
public LayoutParams(RecyclerView.LayoutParams source) {
super(source);
}
}
#Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
calculateWindowSize();
calculateCellPositions(recycler, state);
if (state.getItemCount() == 0) {
detachAndScrapAttachedViews(recycler);
firstVisibleRow = 0;
resetVisibleItemTracking();
return;
}
// TODO use orientationHelper
int startTop = getPaddingTop();
int scrollOffset = 0;
if (forceClearOffsets) { // see #scrollToPosition
startTop = -(firstVisibleRow * cellHeight);
forceClearOffsets = false;
} else if (getChildCount() != 0) {
scrollOffset = getDecoratedTop(getChildAt(0));
startTop = scrollOffset - (firstVisibleRow * cellHeight);
resetVisibleItemTracking();
}
detachAndScrapAttachedViews(recycler);
int row = firstVisibleRow;
int availableSpace = getHeight() - scrollOffset;
int lastItemPosition = state.getItemCount() - 1;
while (availableSpace > 0 && lastVisiblePosition < lastItemPosition) {
availableSpace -= layoutRow(row, startTop, recycler, state);
row = getNextSpannedRow(row);
}
layoutDisappearingViews(recycler, state, startTop);
}
#Override
public RecyclerView.LayoutParams generateDefaultLayoutParams() {
return new LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
}
#Override
public RecyclerView.LayoutParams generateLayoutParams(Context c, AttributeSet attrs) {
return new LayoutParams(c, attrs);
}
#Override
public RecyclerView.LayoutParams generateLayoutParams(ViewGroup.LayoutParams lp) {
if (lp instanceof ViewGroup.MarginLayoutParams) {
return new LayoutParams((ViewGroup.MarginLayoutParams) lp);
} else {
return new LayoutParams(lp);
}
}
#Override
public boolean checkLayoutParams(RecyclerView.LayoutParams lp) {
return lp instanceof LayoutParams;
}
#Override
public void onAdapterChanged(RecyclerView.Adapter oldAdapter, RecyclerView.Adapter newAdapter) {
removeAllViews();
reset();
}
#Override
public boolean supportsPredictiveItemAnimations() {
return true;
}
#Override
public boolean canScrollVertically() {
return true;
}
#Override
public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state){
if (getChildCount() == 0 || dy == 0) return 0;
int scrolled;
int top = getDecoratedTop(getChildAt(0));
if (dy < 0) { // scrolling content down
if (firstVisibleRow == 0) { // at top of content
int scrollRange = -(getPaddingTop() - top);
scrolled = Math.max(dy, scrollRange);
} else {
scrolled = dy;
}
if (top - scrolled >= 0) { // new top row came on screen
int newRow = firstVisibleRow - 1;
if (newRow >= 0) {
int startOffset = top - (firstVisibleRow * cellHeight);
layoutRow(newRow, startOffset, recycler, state);
}
}
int firstPositionOfLastRow = getFirstPositionInSpannedRow(lastVisibleRow);
int lastRowTop = getDecoratedTop(
getChildAt(firstPositionOfLastRow - firstVisiblePosition));
if (lastRowTop - scrolled > getHeight()) { // last spanned row scrolled out
recycleRow(lastVisibleRow, recycler, state);
}
} else { // scrolling content up
int bottom = getDecoratedBottom(getChildAt(getChildCount() - 1));
if (lastVisiblePosition == getItemCount() - 1) { // is at end of content
int scrollRange = Math.max(bottom - getHeight() + getPaddingBottom(), 0);
scrolled = Math.min(dy, scrollRange);
} else {
scrolled = dy;
}
if ((bottom - scrolled) < getHeight()) { // new row scrolled in
int nextRow = lastVisibleRow + 1;
if (nextRow < getSpannedRowCount()) {
int startOffset = top - (firstVisibleRow * cellHeight);
layoutRow(nextRow, startOffset, recycler, state);
}
}
int lastPositionInRow = getLastPositionInSpannedRow(firstVisibleRow, state);
int bottomOfFirstRow =
getDecoratedBottom(getChildAt(lastPositionInRow - firstVisiblePosition));
if (bottomOfFirstRow - scrolled < 0) { // first spanned row scrolled out
recycleRow(firstVisibleRow, recycler, state);
}
}
offsetChildrenVertical(-scrolled);
return scrolled;
}
#Override
public void scrollToPosition(int position) {
if (position >= getItemCount()) position = getItemCount() - 1;
firstVisibleRow = getRowIndex(position);
resetVisibleItemTracking();
forceClearOffsets = true;
removeAllViews();
requestLayout();
}
#Override
public void smoothScrollToPosition(
RecyclerView recyclerView, RecyclerView.State state, int position) {
if (position >= getItemCount()) position = getItemCount() - 1;
LinearSmoothScroller scroller = new LinearSmoothScroller(recyclerView.getContext()) {
#Override
public PointF computeScrollVectorForPosition(int targetPosition) {
final int rowOffset = getRowIndex(targetPosition) - firstVisibleRow;
return new PointF(0, rowOffset * cellHeight);
}
};
scroller.setTargetPosition(position);
startSmoothScroll(scroller);
}
#Override
public int computeVerticalScrollRange(RecyclerView.State state) {
// TODO update this to incrementally calculate
if (firstChildPositionForRow == null) return 0;
return getSpannedRowCount() * cellHeight + getPaddingTop() + getPaddingBottom();
}
#Override
public int computeVerticalScrollExtent(RecyclerView.State state) {
return getHeight();
}
#Override
public int computeVerticalScrollOffset(RecyclerView.State state) {
if (getChildCount() == 0) return 0;
return getPaddingTop() + (firstVisibleRow * cellHeight) - getDecoratedTop(getChildAt(0));
}
#Override
public View findViewByPosition(int position) {
if (position < firstVisiblePosition || position > lastVisiblePosition) return null;
return getChildAt(position - firstVisiblePosition);
}
public int getFirstVisibleItemPosition() {
return firstVisiblePosition;
}
private static class GridCell {
final int row;
final int rowSpan;
final int column;
final int columnSpan;
GridCell(int row, int rowSpan, int column, int columnSpan) {
this.row = row;
this.rowSpan = rowSpan;
this.column = column;
this.columnSpan = columnSpan;
}
}
/**
* This is the main layout algorithm, iterates over all items and places them into [column, row]
* cell positions. Stores this layout info for use later on. Also records the adapter position
* that each row starts at.
* <p>
* Note that if a row is spanned, then the row start position is recorded as the first cell of
* the row that the spanned cell starts in. This is to ensure that we have sufficient contiguous
* views to layout/draw a spanned row.
*/
private void calculateCellPositions(RecyclerView.Recycler recycler, RecyclerView.State state) {
final int itemCount = state.getItemCount();
cells = new SparseArray<>(itemCount);
firstChildPositionForRow = new ArrayList<>();
int row = 0;
int column = 0;
recordSpannedRowStartPosition(row, column);
int[] rowHWM = new int[columns]; // row high water mark (per column)
for (int position = 0; position < itemCount; position++) {
SpanInfo spanInfo;
int adapterPosition = recycler.convertPreLayoutPositionToPostLayout(position);
if (adapterPosition != RecyclerView.NO_POSITION) {
spanInfo = spanLookup.getSpanInfo(adapterPosition);
} else {
// item removed from adapter, retrieve its previous span info
// as we can't get from the lookup (adapter)
spanInfo = getSpanInfoFromAttachedView(position);
}
if (spanInfo.columnSpan > columns) {
spanInfo.columnSpan = columns; // or should we throw?
}
// check horizontal space at current position else start a new row
// note that this may leave gaps in the grid; we don't backtrack to try and fit
// subsequent cells into gaps. We place the responsibility on the adapter to provide
// continuous data i.e. that would not span column boundaries to avoid gaps.
if (column + spanInfo.columnSpan > columns) {
row++;
recordSpannedRowStartPosition(row, position);
column = 0;
}
// check if this cell is already filled (by previous spanning cell)
while (rowHWM[column] > row) {
column++;
if (column + spanInfo.columnSpan > columns) {
row++;
recordSpannedRowStartPosition(row, position);
column = 0;
}
}
// by this point, cell should fit at [column, row]
cells.put(position, new GridCell(row, spanInfo.rowSpan, column, spanInfo.columnSpan));
// update the high water mark book-keeping
for (int columnsSpanned = 0; columnsSpanned < spanInfo.columnSpan; columnsSpanned++) {
rowHWM[column + columnsSpanned] = row + spanInfo.rowSpan;
}
// if we're spanning rows then record the 'first child position' as the first item
// *in the row the spanned item starts*. i.e. the position might not actually sit
// within the row but it is the earliest position we need to render in order to fill
// the requested row.
if (spanInfo.rowSpan > 1) {
int rowStartPosition = getFirstPositionInSpannedRow(row);
for (int rowsSpanned = 1; rowsSpanned < spanInfo.rowSpan; rowsSpanned++) {
int spannedRow = row + rowsSpanned;
recordSpannedRowStartPosition(spannedRow, rowStartPosition);
}
}
// increment the current position
column += spanInfo.columnSpan;
}
totalRows = rowHWM[0];
for (int i = 1; i < rowHWM.length; i++) {
if (rowHWM[i] > totalRows) {
totalRows = rowHWM[i];
}
}
}
private SpanInfo getSpanInfoFromAttachedView(int position) {
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
if (position == getPosition(child)) {
LayoutParams lp = (LayoutParams) child.getLayoutParams();
return new SpanInfo(lp.columnSpan, lp.rowSpan);
}
}
// errrrr?
return SpanInfo.SINGLE_CELL;
}
private void recordSpannedRowStartPosition(final int rowIndex, final int position) {
if (getSpannedRowCount() < (rowIndex + 1)) {
firstChildPositionForRow.add(position);
}
}
private int getRowIndex(final int position) {
return position < cells.size() ? cells.get(position).row : -1;
}
private int getSpannedRowCount() {
return firstChildPositionForRow.size();
}
private int getNextSpannedRow(int rowIndex) {
int firstPositionInRow = getFirstPositionInSpannedRow(rowIndex);
int nextRow = rowIndex + 1;
while (nextRow < getSpannedRowCount()
&& getFirstPositionInSpannedRow(nextRow) == firstPositionInRow) {
nextRow++;
}
return nextRow;
}
private int getFirstPositionInSpannedRow(int rowIndex) {
return firstChildPositionForRow.get(rowIndex);
}
private int getLastPositionInSpannedRow(final int rowIndex, RecyclerView.State state) {
int nextRow = getNextSpannedRow(rowIndex);
return (nextRow != getSpannedRowCount()) ? // check if reached boundary
getFirstPositionInSpannedRow(nextRow) - 1
: state.getItemCount() - 1;
}
/**
* Lay out a given 'row'. We might actually add more that one row if the requested row contains
* a row-spanning cell. Returns the pixel height of the rows laid out.
* <p>
* To simplify logic & book-keeping, views are attached in adapter order, that is child 0 will
* always be the earliest position displayed etc.
*/
private int layoutRow(
int rowIndex, int startTop, RecyclerView.Recycler recycler, RecyclerView.State state) {
int firstPositionInRow = getFirstPositionInSpannedRow(rowIndex);
int lastPositionInRow = getLastPositionInSpannedRow(rowIndex, state);
boolean containsRemovedItems = false;
int insertPosition = (rowIndex < firstVisibleRow) ? 0 : getChildCount();
for (int position = firstPositionInRow;
position <= lastPositionInRow;
position++, insertPosition++) {
View view = recycler.getViewForPosition(position);
LayoutParams lp = (LayoutParams) view.getLayoutParams();
containsRemovedItems |= lp.isItemRemoved();
GridCell cell = cells.get(position);
addView(view, insertPosition);
// TODO use orientation helper
int wSpec = getChildMeasureSpec(
cellBorders[cell.column + cell.columnSpan] - cellBorders[cell.column],
View.MeasureSpec.EXACTLY, 0, lp.width, false);
int hSpec = getChildMeasureSpec(cell.rowSpan * cellHeight,
View.MeasureSpec.EXACTLY, 0, lp.height, true);
measureChildWithDecorationsAndMargin(view, wSpec, hSpec);
int left = cellBorders[cell.column] + lp.leftMargin;
int top = startTop + (cell.row * cellHeight) + lp.topMargin;
int right = left + getDecoratedMeasuredWidth(view);
int bottom = top + getDecoratedMeasuredHeight(view);
layoutDecorated(view, left, top, right, bottom);
lp.columnSpan = cell.columnSpan;
lp.rowSpan = cell.rowSpan;
}
if (firstPositionInRow < firstVisiblePosition) {
firstVisiblePosition = firstPositionInRow;
firstVisibleRow = getRowIndex(firstVisiblePosition);
}
if (lastPositionInRow > lastVisiblePosition) {
lastVisiblePosition = lastPositionInRow;
lastVisibleRow = getRowIndex(lastVisiblePosition);
}
if (containsRemovedItems) return 0; // don't consume space for rows with disappearing items
GridCell first = cells.get(firstPositionInRow);
GridCell last = cells.get(lastPositionInRow);
return (last.row + last.rowSpan - first.row) * cellHeight;
}
/**
* Remove and recycle all items in this 'row'. If the row includes a row-spanning cell then all
* cells in the spanned rows will be removed.
*/
private void recycleRow(
int rowIndex, RecyclerView.Recycler recycler, RecyclerView.State state) {
int firstPositionInRow = getFirstPositionInSpannedRow(rowIndex);
int lastPositionInRow = getLastPositionInSpannedRow(rowIndex, state);
int toRemove = lastPositionInRow;
while (toRemove >= firstPositionInRow) {
int index = toRemove - firstVisiblePosition;
removeAndRecycleViewAt(index, recycler);
toRemove--;
}
if (rowIndex == firstVisibleRow) {
firstVisiblePosition = lastPositionInRow + 1;
firstVisibleRow = getRowIndex(firstVisiblePosition);
}
if (rowIndex == lastVisibleRow) {
lastVisiblePosition = firstPositionInRow - 1;
lastVisibleRow = getRowIndex(lastVisiblePosition);
}
}
private void layoutDisappearingViews(
RecyclerView.Recycler recycler, RecyclerView.State state, int startTop) {
// TODO
}
private void calculateWindowSize() {
// TODO use OrientationHelper#getTotalSpace
int cellWidth =
(int) Math.floor((getWidth() - getPaddingLeft() - getPaddingRight()) / columns);
cellHeight = (int) Math.floor(cellWidth * (1f / cellAspectRatio));
calculateCellBorders();
}
private void reset() {
cells = null;
firstChildPositionForRow = null;
firstVisiblePosition = 0;
firstVisibleRow = 0;
lastVisiblePosition = 0;
lastVisibleRow = 0;
cellHeight = 0;
forceClearOffsets = false;
}
private void resetVisibleItemTracking() {
// maintain the firstVisibleRow but reset other state vars
// TODO make orientation agnostic
int minimumVisibleRow = getMinimumFirstVisibleRow();
if (firstVisibleRow > minimumVisibleRow) firstVisibleRow = minimumVisibleRow;
firstVisiblePosition = getFirstPositionInSpannedRow(firstVisibleRow);
lastVisibleRow = firstVisibleRow;
lastVisiblePosition = firstVisiblePosition;
}
private int getMinimumFirstVisibleRow() {
int maxDisplayedRows = (int) Math.ceil((float) getHeight() / cellHeight) + 1;
if (totalRows < maxDisplayedRows) return 0;
int minFirstRow = totalRows - maxDisplayedRows;
// adjust to spanned rows
return getRowIndex(getFirstPositionInSpannedRow(minFirstRow));
}
/* Adapted from GridLayoutManager */
private void calculateCellBorders() {
cellBorders = new int[columns + 1];
int totalSpace = getWidth() - getPaddingLeft() - getPaddingRight();
int consumedPixels = getPaddingLeft();
cellBorders[0] = consumedPixels;
int sizePerSpan = totalSpace / columns;
int sizePerSpanRemainder = totalSpace % columns;
int additionalSize = 0;
for (int i = 1; i <= columns; i++) {
int itemSize = sizePerSpan;
additionalSize += sizePerSpanRemainder;
if (additionalSize > 0 && (columns - additionalSize) < sizePerSpanRemainder) {
itemSize += 1;
additionalSize -= columns;
}
consumedPixels += itemSize;
cellBorders[i] = consumedPixels;
}
}
private void measureChildWithDecorationsAndMargin(View child, int widthSpec, int heightSpec) {
calculateItemDecorationsForChild(child, itemDecorationInsets);
RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) child.getLayoutParams();
widthSpec = updateSpecWithExtra(widthSpec, lp.leftMargin + itemDecorationInsets.left,
lp.rightMargin + itemDecorationInsets.right);
heightSpec = updateSpecWithExtra(heightSpec, lp.topMargin + itemDecorationInsets.top,
lp.bottomMargin + itemDecorationInsets.bottom);
child.measure(widthSpec, heightSpec);
}
private int updateSpecWithExtra(int spec, int startInset, int endInset) {
if (startInset == 0 && endInset == 0) {
return spec;
}
int mode = View.MeasureSpec.getMode(spec);
if (mode == View.MeasureSpec.AT_MOST || mode == View.MeasureSpec.EXACTLY) {
return View.MeasureSpec.makeMeasureSpec(
View.MeasureSpec.getSize(spec) - startInset - endInset, mode);
}
return spec;
}
/* Adapted from ConstraintLayout */
private void parseAspectRatio(String aspect) {
if (aspect != null) {
int colonIndex = aspect.indexOf(':');
if (colonIndex >= 0 && colonIndex < aspect.length() - 1) {
String nominator = aspect.substring(0, colonIndex);
String denominator = aspect.substring(colonIndex + 1);
if (nominator.length() > 0 && denominator.length() > 0) {
try {
float nominatorValue = Float.parseFloat(nominator);
float denominatorValue = Float.parseFloat(denominator);
if (nominatorValue > 0 && denominatorValue > 0) {
cellAspectRatio = Math.abs(nominatorValue / denominatorValue);
return;
}
} catch (NumberFormatException e) {
// Ignore
}
}
}
}
throw new IllegalArgumentException("Could not parse aspect ratio: '" + aspect + "'");
}
}
attrs.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="SpannedGridLayoutManager">
<attr name="android:orientation" />
<attr name="spanCount" />
<attr name="aspectRatio" format="string" />
</declare-styleable>
</resources>
The code is also available here.
Example usage
The code requires RecyclerView 23.2.0 or higher.
So add the following line to your build.gradle, if you didn't already do so.
dependencies {
compile 'com.android.support:recyclerview-v7:24.2.1'
}
To achieve the layout shown in the initial post, we define the LayoutManager as follows
recyclerView.setLayoutManager(new SpannedGridLayoutManager(
new SpannedGridLayoutManager.GridSpanLookup() {
#Override
public SpannedGridLayoutManager.SpanInfo getSpanInfo(int position) {
if (position == 0) {
return new SpannedGridLayoutManager.SpanInfo(2, 2);
} else {
return new SpannedGridLayoutManager.SpanInfo(1, 1);
}
}
},
3 /* Three columns */,
1f /* We want our items to be 1:1 ratio */));

You can use SpannedGridLayoutManager library wrote by Arasthel in here
This is the result

You can achieve this behavior by using RecycleView for rows only, with ViewHolder for each row. So you will have RowViewHolder for simple rows and something like DoubleRowViewHolder for custom layout that will have 3 items, just the way you want.

Related

Custom Edittext Hiding pin bar after text is inputted

I'm a beginner android developer and trying to create a Custom Pin Edittext using the following code and i want to set the color of the pin to transparent if filled,unfortunately there is no state_filled in the attr of android, how should i do that? Help me please here is my code.
public class PinEntryEditText extends AppCompatEditText {
public static final String XML_NAMESPACE_ANDROID = "http://schemas.android.com/apk/res/android";
private float mSpace = 24; //24 dp by default, space between the lines
private float mCharSize;
private float mNumChars = 6;
private float mLineSpacing = 8; //8dp by default, height of the text from our lines
private int mMaxLength = 6;
private OnClickListener mClickListener;
private float mLineStroke = 1; //1dp by default
private float mLineStrokeSelected = 2; //2dp by default
private Paint mLinesPaint;
int[][] mStates = new int[][]{
new int[]{android.R.attr.state_selected}, // selected
new int[]{android.R.attr.state_focused}, // focused
new int[]{-android.R.attr.state_focused}, // unfocused
};
//Green color = 0xFFB6C800
//Gray color = 0xFFCCCCCC
int[] mColors = new int[]{
0xFFB6C800,
0xFFCCCCCC,
0xFF880000,
};
ColorStateList mColorStates = new ColorStateList(mStates, mColors);
public PinEntryEditText(Context context) {
super(context);
}
public PinEntryEditText(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
public PinEntryEditText(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs);
}
private void init(Context context, AttributeSet attrs) {
float multi = context.getResources().getDisplayMetrics().density;
mLineStroke = multi * mLineStroke;
mLineStrokeSelected = multi * mLineStrokeSelected;
mLinesPaint = new Paint(getPaint());
mLinesPaint.setStrokeWidth(mLineStroke);
setBackgroundResource(0);
mSpace = multi * mSpace; //convert to pixels for our density
mLineSpacing = multi * mLineSpacing; //convert to pixels for our density
mMaxLength = attrs.getAttributeIntValue(XML_NAMESPACE_ANDROID, "maxLength", 4);
mNumChars = mMaxLength;
//Disable copy paste
super.setCustomSelectionActionModeCallback(new ActionMode.Callback() {
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
return false;
}
public void onDestroyActionMode(ActionMode mode) {
}
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
return false;
}
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
return false;
}
});
// When tapped, move cursor to end of text.
super.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
setSelection(getText().length());
if (mClickListener != null) {
mClickListener.onClick(v);
}
}
});
}
#Override
public void setOnClickListener(OnClickListener l) {
mClickListener = l;
}
#Override
public void setCustomSelectionActionModeCallback(ActionMode.Callback actionModeCallback) {
throw new RuntimeException("setCustomSelectionActionModeCallback() not supported.");
}
#Override
protected void onDraw(Canvas canvas) {
//super.onDraw(canvas);
int availableWidth = getWidth() - getPaddingRight() - getPaddingLeft();
if (mSpace < 0) {
mCharSize = (availableWidth / (mNumChars * 2 - 1));
} else {
mCharSize = (availableWidth - (mSpace * (mNumChars - 1))) / mNumChars;
}
int startX = getPaddingLeft();
int bottom = getHeight() - getPaddingBottom();
//Text Width
Editable text = getText();
int textLength = text.length();
float[] textWidths = new float[textLength];
getPaint().getTextWidths(getText(), 0, textLength, textWidths);
for (int i = 0; i < mNumChars; i++) {
updateColorForLines(i == textLength);
canvas.drawLine(startX, bottom, startX + mCharSize, bottom, mLinesPaint);
if (getText().length() > i) {
float middle = startX + mCharSize / 2;
canvas.drawText(text, i, i + 1, middle - textWidths[0] / 2, bottom - mLineSpacing, getPaint());
}
if (mSpace < 0) {
startX += mCharSize * 2;
} else {
startX += mCharSize + mSpace;
}
}
//mLinesPaint.setColor(getColorForState(0xff1a1f71));
}
private int getColorForState(int... states) {
return mColorStates.getColorForState(states, Color.GRAY);
}
/**
* #param next Is the current char the next character to be input?
*/
private void updateColorForLines(boolean next) {
if (isFocused()) {
mLinesPaint.setStrokeWidth(mLineStrokeSelected);
mLinesPaint.setColor(getColorForState(android.R.attr.state_focused));
if (next) {
mLinesPaint.setColor(getColorForState(android.R.attr.state_selected));
}
} else {
mLinesPaint.setStrokeWidth(mLineStroke);
mLinesPaint.setColor(getColorForState(-android.R.attr.state_focused));
}
}
}
Is there a way to do it?
there is no state_filled but you can create one. you can use TextWatcher interface it will be triggered whenever text will be updated. you can check there if current text size is equal to your mMaxLength set your pin color to transparent or else.
here is an example of TextWatcher implementation :
et1.addTextChangedListener(new TextWatcher() {
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
// TODO Auto-generated method stub
}
#Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
// TODO Auto-generated method stub
}
#Override
public void afterTextChanged(Editable s) {
// TODO Auto-generated method stub
}
});

RecyclerView horizontal scrolling to left

I'm making an app where I'm using RecyclerView with SnapHelper class to make a scrollable horizontal card stack. I have implemented that successfully however my problem is that the cards swipe to right direction and I want to swipe the cards on left direction.
I did some research on Stack Overflow and found 1 and 2
But I still couldn't figure out how to set the scrolling position to left instead of right.
I also found that FindSnapView method passes the scroll position and direction maybe I'm wrong but below code might be the solution
#Override
public View findSnapView(RecyclerView.LayoutManager layoutManager) {
if (layoutManager instanceof LadderLayoutManager) {
int pos = ((LadderLayoutManager) layoutManager)
.getFixedScrollPosition(mDirection, mDirection != 0 ? 0.8f : 0.5f);
mDirection = 0;
if (pos != RecyclerView.NO_POSITION) {
return layoutManager.findViewByPosition(pos);
}
}
return null;
}
My main class
public class MainActivity extends AppCompatActivity {
LadderLayoutManager llm;
RecyclerView rcv;
HSAdapter adapter;
int scrollToPosition;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
llm = new LadderLayoutManager(1.5f, 0.85f, LadderLayoutManager.HORIZONTAL).
setChildDecorateHelper(new LadderLayoutManager
.DefaultChildDecorateHelper(getResources().getDimension(R.dimen.item_max_elevation)));
llm.setChildPeekSize((int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
30, getResources().getDisplayMetrics()));
llm.setMaxItemLayoutCount(5);
rcv = (RecyclerView) findViewById(R.id.rcv);
rcv.setLayoutManager(llm);
new LadderSimpleSnapHelper().attachToRecyclerView(rcv);
adapter = new HSAdapter();
rcv.setAdapter(adapter);
final SeekBar sb = (SeekBar) findViewById(R.id.sb);
}
My code for custom LayoutManager
public class LadderLayoutManager extends RecyclerView.LayoutManager implements RecyclerView.SmoothScroller.ScrollVectorProvider {
private static final int INVALIDATE_SCROLL_OFFSET = Integer.MAX_VALUE;
private static final float DEFAULT_CHILD_LAYOUT_OFFSET = 0.2f;
public static final int UNLIMITED = 0;
public static final int VERTICAL = 1;
public static final int HORIZONTAL = 0;
private boolean mCheckedChildSize;
private int[] mChildSize;
private int mChildPeekSize;
private int mChildPeekSizeInput;
private boolean mReverse;
private int mScrollOffset = INVALIDATE_SCROLL_OFFSET;
private float mItemHeightWidthRatio;
private float mScale;
private int mChildCount;
private float mVanishOffset = 0;
private Interpolator mInterpolator;
private int mOrientation;
private ChildDecorateHelper mDecorateHelper;
private int mMaxItemLayoutCount;
public LadderLayoutManager(float itemHeightWidthRatio) {
this(itemHeightWidthRatio, 0.9f, VERTICAL);
}
public LadderLayoutManager(float itemHeightWidthRatio, float scale, int orientation) {
this.mItemHeightWidthRatio = itemHeightWidthRatio;
this.mOrientation = orientation;
this.mScale = scale;
this.mChildSize = new int[2];
this.mInterpolator = new DecelerateInterpolator();
}
#Override
public RecyclerView.LayoutParams generateDefaultLayoutParams() {
return new RecyclerView.LayoutParams(mChildSize[0], mChildSize[1]);
}
public LadderLayoutManager setChildDecorateHelper(ChildDecorateHelper layoutHelper) {
mDecorateHelper = layoutHelper;
return this;
}
public void setMaxItemLayoutCount(int count) {
mMaxItemLayoutCount = Math.max(2, count);
if (getChildCount() > 0) {
requestLayout();
}
}
public void setVanishOffset(float offset) {
mVanishOffset = offset;
if (getChildCount() > 0) {
requestLayout();
}
}
public void setChildPeekSize(int childPeekSize) {
mChildPeekSizeInput = childPeekSize;
mCheckedChildSize = false;
if (getChildCount() > 0) {
requestLayout();
}
}
public void setItemHeightWidthRatio(float itemHeightWidthRatio) {
mItemHeightWidthRatio = itemHeightWidthRatio;
mCheckedChildSize = false;
if (getChildCount() > 0) {
requestLayout();
}
}
public void setReverse(boolean reverse) {
if (mReverse != reverse) {
mReverse = reverse;
if (getChildCount() > 0) {
requestLayout();
}
}
}
public boolean isReverse() {
return mReverse;
}
public int getFixedScrollPosition(int direction, float fixValue) {
if (mCheckedChildSize) {
if (mScrollOffset % mChildSize[mOrientation] == 0) {
return RecyclerView.NO_POSITION;
}
float position = mScrollOffset * 1.0f / mChildSize[mOrientation];
return convert2AdapterPosition((int) (direction > 0 ? position + fixValue : position + (1 - fixValue)) - 1);
}
return RecyclerView.NO_POSITION;
}
#Override
public void onMeasure(RecyclerView.Recycler recycler, RecyclerView.State state, int widthSpec, int heightSpec) {
super.onMeasure(recycler, state, widthSpec, heightSpec);
mCheckedChildSize = false;
}
#Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
if (state.getItemCount() == 0) {
removeAndRecycleAllViews(recycler);
return;
}
if (!mCheckedChildSize) {
if (mOrientation == VERTICAL) {
mChildSize[0] = getHorizontalSpace();
mChildSize[1] = (int) (mItemHeightWidthRatio * mChildSize[0]);
} else {
mChildSize[1] = getVerticalSpace();
mChildSize[0] = (int) (mChildSize[1] / mItemHeightWidthRatio);
}
mChildPeekSize = mChildPeekSizeInput == 0 ?
(int) (mChildSize[mOrientation] * DEFAULT_CHILD_LAYOUT_OFFSET) : mChildPeekSizeInput;
mCheckedChildSize = true;
}
int itemCount = getItemCount();
if (mReverse) {
mScrollOffset += (itemCount - mChildCount) * mChildSize[mOrientation];
}
mChildCount = itemCount;
mScrollOffset = makeScrollOffsetWithinRange(mScrollOffset);
fill(recycler);
}
public void fill(RecyclerView.Recycler recycler) {
int bottomItemPosition = (int) Math.floor(mScrollOffset / mChildSize[mOrientation]);//>=1
int bottomItemVisibleSize = mScrollOffset % mChildSize[mOrientation];
final float offsetPercent = mInterpolator.getInterpolation(
bottomItemVisibleSize * 1.0f / mChildSize[mOrientation]);//[0,1)
final int space = mOrientation == VERTICAL ? getVerticalSpace() : getHorizontalSpace();
ArrayList<ItemLayoutInfo> layoutInfos = new ArrayList<>();
for (int i = bottomItemPosition - 1, j = 1, remainSpace = space - mChildSize[mOrientation];
i >= 0; i--, j++) {
double maxOffset = mChildPeekSize * Math.pow(mScale, j);
int start = (int) (remainSpace - offsetPercent * maxOffset);
ItemLayoutInfo info = new ItemLayoutInfo(start,
(float) (Math.pow(mScale, j - 1) * (1 - offsetPercent * (1 - mScale))),
offsetPercent,
start * 1.0f / space
);
layoutInfos.add(0, info);
if (mMaxItemLayoutCount != UNLIMITED && j == mMaxItemLayoutCount - 1) {
if (offsetPercent != 0) {
info.start = remainSpace;
info.positionOffsetPercent = 0;
info.layoutPercent = remainSpace / space;
info.scaleXY = (float) Math.pow(mScale, j - 1);
}
break;
}
remainSpace -= maxOffset;
if (remainSpace <= 0) {
info.start = (int) (remainSpace + maxOffset);
info.positionOffsetPercent = 0;
info.layoutPercent = info.start / space;
info.scaleXY = (float) Math.pow(mScale, j - 1);
break;
}
}
if (bottomItemPosition < mChildCount) {
final int start = space - bottomItemVisibleSize;
layoutInfos.add(new ItemLayoutInfo(start, 1.0f,
bottomItemVisibleSize * 1.0f / mChildSize[mOrientation], start * 1.0f / space).
setIsBottom());
} else {
bottomItemPosition -= 1;
}
int layoutCount = layoutInfos.size();
final int startPos = bottomItemPosition - (layoutCount - 1);
final int endPos = bottomItemPosition;
final int childCount = getChildCount();
for (int i = childCount - 1; i >= 0; i--) {
View childView = getChildAt(i);
int pos = convert2LayoutPosition(getPosition(childView));
if (pos > endPos || pos < startPos) {
removeAndRecycleView(childView, recycler);
}
}
detachAndScrapAttachedViews(recycler);
for (int i = 0; i < layoutCount; i++) {
fillChild(recycler.getViewForPosition(convert2AdapterPosition(startPos + i)), layoutInfos.get(i));
}
}
private void fillChild(View view, ItemLayoutInfo layoutInfo) {
addView(view);
measureChildWithExactlySize(view);
final int scaleFix = (int) (mChildSize[mOrientation] * (1 - layoutInfo.scaleXY) / 2);
final float gap = (mOrientation == VERTICAL ? getHorizontalSpace() : getVerticalSpace())
- mChildSize[(mOrientation + 1) % 2] * layoutInfo.scaleXY;
if (mOrientation == VERTICAL) {
int left = (int) (getPaddingLeft() + (gap * 0.5 * mVanishOffset));
layoutDecoratedWithMargins(view, left, layoutInfo.start - scaleFix
, left + mChildSize[0], layoutInfo.start + mChildSize[1] - scaleFix);
} else {
int top = (int) (getPaddingTop() + (gap * 0.5 * mVanishOffset));
layoutDecoratedWithMargins(view, layoutInfo.start - scaleFix, top
, layoutInfo.start + mChildSize[0] - scaleFix, top + mChildSize[1]);
}
ViewCompat.setScaleX(view, layoutInfo.scaleXY);
ViewCompat.setScaleY(view, layoutInfo.scaleXY);
if (mDecorateHelper != null) {
mDecorateHelper.decorateChild(view, layoutInfo.positionOffsetPercent, layoutInfo.layoutPercent, layoutInfo.isBottom);
}
}
private void measureChildWithExactlySize(View child) {
RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) child.getLayoutParams();
final int widthSpec = View.MeasureSpec.makeMeasureSpec(
mChildSize[0] - lp.leftMargin - lp.rightMargin, View.MeasureSpec.EXACTLY);
final int heightSpec = View.MeasureSpec.makeMeasureSpec(
mChildSize[1] - lp.topMargin - lp.bottomMargin, View.MeasureSpec.EXACTLY);
child.measure(widthSpec, heightSpec);
}
private int makeScrollOffsetWithinRange(int scrollOffset) {
return Math.min(Math.max(mChildSize[mOrientation], scrollOffset), mChildCount * mChildSize[mOrientation]);
}
#Override
public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
int pendingScrollOffset = mScrollOffset + dy;
mScrollOffset = makeScrollOffsetWithinRange(pendingScrollOffset);
fill(recycler);
return mScrollOffset - pendingScrollOffset + dy;
}
#Override
public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler, RecyclerView.State state) {
int pendingScrollOffset = mScrollOffset + dx;
mScrollOffset = makeScrollOffsetWithinRange(pendingScrollOffset);
fill(recycler);
return mScrollOffset - pendingScrollOffset + dx;
}
#Override
public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state, int position) {
final LinearSmoothScroller linearSmoothScroller = new LinearSmoothScroller(recyclerView.getContext()) {
#Override
public int calculateDyToMakeVisible(final View view, final int snapPreference) {
if (mOrientation == VERTICAL) {
return -calculateDistanceToPosition(getPosition(view));
}
return 0;
}
#Override
public int calculateDxToMakeVisible(final View view, final int snapPreference) {
if (mOrientation == HORIZONTAL) {
return -calculateDistanceToPosition(getPosition(view));
}
return 0;
}
};
linearSmoothScroller.setTargetPosition(position);
startSmoothScroll(linearSmoothScroller);
}
public int calculateDistanceToPosition(int targetPos) {
int pendingScrollOffset = mChildSize[mOrientation] * (convert2LayoutPosition(targetPos) + 1);
return pendingScrollOffset - mScrollOffset;
}
#Override
public void scrollToPosition(int position) {
if (position > 0 && position < mChildCount) {
mScrollOffset = mChildSize[mOrientation] * (convert2LayoutPosition(position) + 1);
requestLayout();
}
}
#Override
public boolean canScrollVertically() {
return mOrientation == VERTICAL;
}
#Override
public boolean canScrollHorizontally() {
return mOrientation == HORIZONTAL;
}
public int convert2AdapterPosition(int layoutPosition) {
return mReverse ? mChildCount - 1 - layoutPosition : layoutPosition;
}
public int convert2LayoutPosition(int adapterPostion) {
return mReverse ? mChildCount - 1 - adapterPostion : adapterPostion;
}
public int getVerticalSpace() {
return getHeight() - getPaddingTop() - getPaddingBottom();
}
public int getHorizontalSpace() {
return getWidth() - getPaddingLeft() - getPaddingRight();
}
#Override
public PointF computeScrollVectorForPosition(int targetPosition) {
int pos = convert2LayoutPosition(targetPosition);
int scrollOffset = (pos + 1) * mChildSize[mOrientation];
return mOrientation == VERTICAL ? new PointF(0, Math.signum(scrollOffset - mScrollOffset))
: new PointF(Math.signum(scrollOffset - mScrollOffset), 0);
}
private static class ItemLayoutInfo {
float scaleXY;
float layoutPercent;
float positionOffsetPercent;
int start;
boolean isBottom;
ItemLayoutInfo(int top, float scale, float positonOffset, float percent) {
this.start = top;
this.scaleXY = scale;
this.positionOffsetPercent = positonOffset;
this.layoutPercent = percent;
}
ItemLayoutInfo setIsBottom() {
isBottom = true;
return this;
}
}
#Override
public Parcelable onSaveInstanceState() {
SavedState savedState = new SavedState();
savedState.scrollOffset = mScrollOffset;
savedState.reverse = mReverse;
savedState.vanishOffset = mVanishOffset;
savedState.scale = mScale;
savedState.childLayoutOffsetInput = mChildPeekSizeInput;
savedState.itemHeightWidthRatio = mItemHeightWidthRatio;
savedState.orientation = mOrientation;
return savedState;
}
#Override
public void onRestoreInstanceState(Parcelable state) {
if (state instanceof SavedState) {
SavedState s = (SavedState) state;
mScrollOffset = s.scrollOffset;
mReverse = s.reverse;
mVanishOffset = s.vanishOffset;
mScale = s.scale;
mChildPeekSizeInput = s.childLayoutOffsetInput;
mItemHeightWidthRatio = s.itemHeightWidthRatio;
mOrientation = s.orientation;
requestLayout();
}
}
public static class SavedState implements Parcelable {
int scrollOffset, childLayoutOffsetInput, orientation;
float itemHeightWidthRatio, scale, elevation, vanishOffset;
boolean reverse;
public SavedState() {
}
SavedState(LadderLayoutManager.SavedState other) {
scrollOffset = other.scrollOffset;
childLayoutOffsetInput = other.childLayoutOffsetInput;
orientation = other.orientation;
itemHeightWidthRatio = other.itemHeightWidthRatio;
scale = other.scale;
elevation = other.elevation;
vanishOffset = other.vanishOffset;
reverse = other.reverse;
}
SavedState(Parcel in) {
scrollOffset = in.readInt();
childLayoutOffsetInput = in.readInt();
orientation = in.readInt();
itemHeightWidthRatio = in.readFloat();
scale = in.readFloat();
elevation = in.readFloat();
vanishOffset = in.readFloat();
reverse = in.readInt() == 1;
}
#Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(scrollOffset);
dest.writeInt(childLayoutOffsetInput);
dest.writeInt(orientation);
dest.writeFloat(itemHeightWidthRatio);
dest.writeFloat(scale);
dest.writeFloat(elevation);
dest.writeFloat(vanishOffset);
dest.writeInt(reverse ? 1 : 0);
}
#Override
public int describeContents() {
return 0;
}
public static final Creator<SavedState> CREATOR
= new Creator<SavedState>() {
#Override
public LadderLayoutManager.SavedState createFromParcel(Parcel in) {
return new LadderLayoutManager.SavedState(in);
}
#Override
public LadderLayoutManager.SavedState[] newArray(int size) {
return new LadderLayoutManager.SavedState[size];
}
};
}
public interface ChildDecorateHelper {
void decorateChild(View child, float posOffsetPercent, float layoutPercent, boolean isBottom);
}
public static class DefaultChildDecorateHelper implements ChildDecorateHelper {
private float mElevation;
public DefaultChildDecorateHelper(float maxElevation) {
mElevation = maxElevation;
}
#Override
public void decorateChild(View child, float posOffsetPercent, float layoutPercent, boolean isBottom) {
ViewCompat.setElevation(child, (float) (layoutPercent * mElevation * 0.7 + mElevation * 0.3));
}
}
}
Here is my code for snaphelper
public class LadderSimpleSnapHelper extends SnapHelper {
private int mDirection;
//int position = layoutManager.getPosition(centerView);
#Override
public int[] calculateDistanceToFinalSnap(
#NonNull RecyclerView.LayoutManager layoutManager, #NonNull View targetView) {
if (layoutManager instanceof LadderLayoutManager) {
int[] out = new int[2];
if (layoutManager.canScrollHorizontally()) {
out[0] = ((LadderLayoutManager) layoutManager).calculateDistanceToPosition(
layoutManager.getPosition(targetView));
out[1] = 0;
} else {
out[0] = 0;
out[1] = ((LadderLayoutManager) layoutManager).calculateDistanceToPosition(
layoutManager.getPosition(targetView));
}
return out;
}
return null;
}
#Override
public int findTargetSnapPosition(RecyclerView.LayoutManager layoutManager, int velocityX,
int velocityY) {
if (layoutManager.canScrollHorizontally()) {
mDirection = velocityX;
} else {
mDirection = velocityY;
}
return RecyclerView.NO_POSITION;
}
#Override
public View findSnapView(RecyclerView.LayoutManager layoutManager) {
if (layoutManager instanceof LadderLayoutManager) {
int pos = ((LadderLayoutManager) layoutManager).getFixedScrollPosition(
mDirection, mDirection != 0 ? 0.8f : 0.5f);
mDirection = 0;
if (pos != RecyclerView.NO_POSITION) {
return layoutManager.findViewByPosition(pos);
}
}
return null;
}
}
try below code :
mRecyclerView.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, true));
mRecyclerView.setReverseLayout(true);
Use the code below:
mRecyclerView.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, true));
in xml:
right to left
android:layoutDirection="rtl"
left to right
android:layoutDirection="ltr"

Setter/Getter method not working

Hi I am having a problem accessing variables/methods in my view class from my main activity. I just need to return the turns int from GameView.java and be able to access it in MainActivity and then insert it into my database. I try to use GameView mGameView = new GameView(getApplicationContext()); but this doesn't work and I can't access any methods from GameView with this.
My GameView class:
public class GameView extends View {
int mcolumns = 5;
int mrows = 5;
private NetwalkGrid mGame = new NetwalkGrid(mcolumns, mrows);
private GestureDetector mGestureDetector;
Random rand = new Random();
int sizeSqX;
int sizeSqY;
int sizeSq;
int turns = 0;
Paint bgPaint;
public GameView(Context context) {
super(context);
init();
}
private void init() {
bgPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
bgPaint.setStyle(Paint.Style.FILL);
bgPaint.setColor(0xff0000ff);
mGame.gridCopy();
for (int col = 0; col < mGame.getColumns(); col++) {
for (int row = 0; row < mGame.getRows(); row++) {
int num = rand.nextInt(3) + 1;
for (int turns = 1; turns < num; turns++) {
mGame.rotateRight(col, row);
}
}
}
}
#Override
protected void onDraw(Canvas canvas) {
canvas.drawColor(Color.BLACK);
sizeSqX = getWidth() / mcolumns;
sizeSqY = getHeight() / mrows;
if (sizeSqX < sizeSqY) {
sizeSq = sizeSqX;
}
else {
sizeSq = sizeSqY;
}
Bitmap b = BitmapFactory.decodeResource(getResources(), R.drawable.placeholder);
int cellContent;
int square = 140;
for (int col = 0; col < mGame.getColumns(); col++) {
for (int row = 0; row < mGame.getRows(); row++) {
cellContent = mGame.getGridElem(col,row);
if (cellContent == 1) {
b = BitmapFactory.decodeResource(getResources(), R.drawable.up_down); // Image for down position
if (cellContent == 65 && mGame.checkWin()) {
b = BitmapFactory.decodeResource(getResources(), R.drawable.up_down_connected);
}
}
else if (cellContent == 2) {
b = BitmapFactory.decodeResource(getResources(), R.drawable.left_right); // Right position
if (mGame.checkWin()) {
b = BitmapFactory.decodeResource(getResources(), R.drawable.left_right_connected);
}
}
else if (cellContent == 3) {
b = BitmapFactory.decodeResource(getResources(), R.drawable.right_down); // Down right position WORKS
if (mGame.checkWin()) {
b = BitmapFactory.decodeResource(getResources(), R.drawable.right_down_connected);
}
}
else {
b = BitmapFactory.decodeResource(getResources(), R.drawable.placeholder2); //
}
canvas.drawBitmap(b, null,new Rect(col * sizeSq, row * sizeSq,col*sizeSq+sizeSq, row*sizeSq+sizeSq), null);
TextPaint tp = new TextPaint();
tp.setColor(Color.GREEN);
tp.setTextSize(70);
tp.setTypeface(Typeface.create("Courier", Typeface.BOLD));
canvas.drawText("Moves: " + String.valueOf(turns), 10, 1180, tp);
}
}
}
#Override
public boolean onTouchEvent(MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();
int column = getColTouched(event.getX());
int row = getRowTouched(event.getY());
try {
mGame.rotateRight(column, row);
System.out.println(mcolumns);
mGame.checkWin();
if (mGame.checkWin()) {
System.out.println("check win works");
invalidate();
}
invalidate();
}
catch (ArrayIndexOutOfBoundsException err) {
System.out.println("User has pressed outside game grid - exception caught");
}
turns++;
return super.onTouchEvent(event);
}
public int getColTouched(float x) {
return (int) (x / sizeSq);
}
public int getRowTouched(float y) {
return (int) (y / sizeSq);
}
public int getTurns() {
return turns;
}
}
MainActivity:
public class MainActivity extends AppCompatActivity {
private int mcolumns;
private int mrows;
DatabaseHelper myDb;
String test = "test data";
int turns;
static Context appcon;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
appcon = this;
myDb = new DatabaseHelper(this);
}
public void runGame(View view){
Intent intent = new Intent(this, GameViewActivity.class);
startActivity(intent);
}
public void runInstructions(View view) {
Intent intent = new Intent(this, InstructionsActivity.class);
startActivity(intent);
}
public void setTurns() {
GameView mGameView = new GameView(getApplicationContext());
this.turns = mGameView.turns;
System.out.println("Turns: " + Integer.toString(turns));
}
public void AddData() {
boolean isInserted = myDb.insertData(Integer.toString(turns));
if(isInserted == true) {
System.out.println("Data inserted");
}
else {
System.out.println("Data NOT inserted");
}
}
}
In MainAcivity, inside setTurns()
Replace this
this.turns = mGameView.turns; //you can't access this variable directly
with
this.turns = mGameView.getTurns(); // Calling the public method getTurns()
please see this answer for explanation about Access modifiers in Java
There are two ways to fix this.
1. Preferred way:
Add a new Method in GameView class
public int getTurns() {
return turns;
}
And use this method where you want to access turns like:
this.turns = mGameView.getTurns();
2. Bad way:
Make turns variable public.

Drag and Drop Dynamic Listview Row Disappears When Adding Item to Listview Android

So I am trying to make a Listview you can drag and drop the rows. I got it to work ok, but when I add an item to the Listview it breaks (here is what the bug looks like: http://youtu.be/VYRYh0Vwq6o). What is causing this problem and how do I fix it? Thanks in advance.
Heres my code:
ItemsArrayAdapter.xml
package benawad.com.todolist.adapters;
import android.content.Context;
import android.graphics.Paint;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.TextView;
import java.util.ArrayList;
import java.util.HashMap;
import benawad.com.todolist.NoteActivity;
import benawad.com.todolist.R;
public class ItemsArrayAdapter extends ArrayAdapter<String> {
public static final String TAG = ItemsArrayAdapter.class.getSimpleName();
Context mContext;
ArrayList<String> mArrayList;
boolean wantsSlash;
NoteActivity mNoteActivity;
EditText mNewItemText;
final int INVALID_ID = -1;
HashMap<String, Integer> mIdMap = new HashMap<String, Integer>();
public ItemsArrayAdapter
(NoteActivity context, ArrayList<String> arrayList, boolean slashes){
super(context, R.layout.item_row,arrayList);
mContext = context;
mArrayList = arrayList;
mNoteActivity = context;
wantsSlash = slashes;
update();
}
public View getView(final int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if(convertView == null){
convertView = LayoutInflater.from(mContext).inflate(R.layout.item_row, parent, false);
holder = new ViewHolder();
holder.itemName = (TextView)convertView.findViewById(R.id.itemText);
for (int i = 0; i < mArrayList.size(); ++i) {
mIdMap.put(mArrayList.get(i), i);
}
holder.delete = (ImageView)convertView.findViewById(R.id.delete_item);
holder.edit = (ImageView)convertView.findViewById(R.id.edit_item);
convertView.setTag(holder);
}
else{
holder = (ViewHolder) convertView.getTag();
}
holder.itemName.setText(mArrayList.get(position));
if(wantsSlash){
holder.itemName.setPaintFlags(Paint.STRIKE_THRU_TEXT_FLAG);
}
holder.delete.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
mNoteActivity.deleteItem(position);
}
});
holder.edit.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
mNoteActivity.editItem(position);
}
});
return convertView;
}
private static class ViewHolder {
TextView itemName;
ImageView edit;
ImageView delete;
}
#Override
public long getItemId(int position) {
Long idToReturn = (long) INVALID_ID;
if (position >= 0 && position <= mIdMap.size()-1) {
try {
String item = getItem(position);
idToReturn = (long) mIdMap.get(item);
}
catch(Exception e){}
}
return idToReturn;
}
// #Override
// public long getItemId(int position) {
// if (position < 0 || position >= mIdMap.size()) {
// return INVALID_ID;
// }
// String item = getItem(position);
// return mIdMap.get(item);
// }
public void update(){
for (int i = 0; i < mArrayList.size(); ++i) {
mIdMap.put(mArrayList.get(i), i);
}
}
#Override
public boolean hasStableIds() {
return true;
}
}
DynamicListView.java
package benawad.com.todolist;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.animation.TypeEvaluator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.drawable.BitmapDrawable;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewTreeObserver;
import android.widget.AbsListView;
import android.widget.AdapterView;
import android.widget.BaseAdapter;
import android.widget.ListView;
import java.util.ArrayList;
import benawad.com.todolist.adapters.ItemsArrayAdapter;
public class DynamicListView extends ListView {
final static String TAG = DynamicListView.class.getSimpleName();
private final int SMOOTH_SCROLL_AMOUNT_AT_EDGE = 15;
private final int MOVE_DURATION = 150;
private final int LINE_THICKNESS = 15;
public ArrayList<String> mCheeseList;
private int mLastEventY = -1;
private int mDownY = -1;
private int mDownX = -1;
private int mTotalOffset = 0;
private boolean mCellIsMobile = false;
private boolean mIsMobileScrolling = false;
private int mSmoothScrollAmountAtEdge = 0;
private final int INVALID_ID = -1;
private long mAboveItemId = INVALID_ID;
private long mMobileItemId = INVALID_ID;
private long mBelowItemId = INVALID_ID;
private BitmapDrawable mHoverCell;
private Rect mHoverCellCurrentBounds;
private Rect mHoverCellOriginalBounds;
private final int INVALID_POINTER_ID = -1;
private int mActivePointerId = INVALID_POINTER_ID;
private boolean mIsWaitingForScrollFinish = false;
private int mScrollState = OnScrollListener.SCROLL_STATE_IDLE;
public DynamicListView(Context context) {
super(context);
init(context);
}
public DynamicListView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context);
}
public DynamicListView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public void init(Context context) {
setOnItemLongClickListener(mOnItemLongClickListener);
setOnScrollListener(mScrollListener);
DisplayMetrics metrics = context.getResources().getDisplayMetrics();
mSmoothScrollAmountAtEdge = (int)(SMOOTH_SCROLL_AMOUNT_AT_EDGE / metrics.density);
}
/**
* Listens for long clicks on any mItems in the listview. When a cell has
* been selected, the hover cell is created and set up.
*/
private OnItemLongClickListener mOnItemLongClickListener =
new OnItemLongClickListener() {
public boolean onItemLongClick(AdapterView<?> arg0, View arg1, int pos, long id) {
mTotalOffset = 0;
int position = pointToPosition(mDownX, mDownY);
int itemNum = position - getFirstVisiblePosition();
View selectedView = getChildAt(itemNum);
mMobileItemId = getAdapter().getItemId(position);
mHoverCell = getAndAddHoverView(selectedView);
selectedView.setVisibility(INVISIBLE);
mCellIsMobile = true;
updateNeighborViewsForID(mMobileItemId);
return true;
}
};
/**
* Creates the hover cell with the appropriate bitmap and of appropriate
* size. The hover cell's BitmapDrawable is drawn on top of the bitmap every
* single time an invalidate call is made.
*/
private BitmapDrawable getAndAddHoverView(View v) {
int w = v.getWidth();
int h = v.getHeight();
int top = v.getTop();
int left = v.getLeft();
Bitmap b = getBitmapWithBorder(v);
BitmapDrawable drawable = new BitmapDrawable(getResources(), b);
mHoverCellOriginalBounds = new Rect(left, top, left + w, top + h);
mHoverCellCurrentBounds = new Rect(mHoverCellOriginalBounds);
drawable.setBounds(mHoverCellCurrentBounds);
return drawable;
}
/** Draws a black border over the screenshot of the view passed in. */
private Bitmap getBitmapWithBorder(View v) {
Bitmap bitmap = getBitmapFromView(v);
Canvas can = new Canvas(bitmap);
Rect rect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight());
Paint paint = new Paint();
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(LINE_THICKNESS);
paint.setColor(Color.BLACK);
can.drawBitmap(bitmap, 0, 0, null);
can.drawRect(rect, paint);
return bitmap;
}
/** Returns a bitmap showing a screenshot of the view passed in. */
private Bitmap getBitmapFromView(View v) {
Bitmap bitmap = Bitmap.createBitmap(v.getWidth(), v.getHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas (bitmap);
v.draw(canvas);
return bitmap;
}
/**
* Stores a reference to the views above and below the item currently
* corresponding to the hover cell. It is important to note that if this
* item is either at the top or bottom of the list, mAboveItemId or mBelowItemId
* may be invalid.
*/
private void updateNeighborViewsForID(long itemID) {
int position = getPositionForID(itemID);
ItemsArrayAdapter adapter = ((ItemsArrayAdapter)getAdapter());
mAboveItemId = adapter.getItemId(position - 1);
mBelowItemId = adapter.getItemId(position + 1);
}
/** Retrieves the view in the list corresponding to itemID */
public View getViewForID (long itemID) {
int firstVisiblePosition = getFirstVisiblePosition();
ItemsArrayAdapter adapter = ((ItemsArrayAdapter)getAdapter());
for(int i = 0; i < getChildCount(); i++) {
View v = getChildAt(i);
int position = firstVisiblePosition + i;
long id = adapter.getItemId(position);
if (id == itemID) {
return v;
}
}
return null;
}
/** Retrieves the position in the list corresponding to itemID */
public int getPositionForID (long itemID) {
View v = getViewForID(itemID);
if (v == null) {
return -1;
} else {
return getPositionForView(v);
}
}
/**
* dispatchDraw gets invoked when all the child views are about to be drawn.
* By overriding this method, the hover cell (BitmapDrawable) can be drawn
* over the listview's mItems whenever the listview is redrawn.
*/
#Override
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
if (mHoverCell != null) {
mHoverCell.draw(canvas);
}
}
#Override
public boolean onTouchEvent (MotionEvent event) {
switch (event.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
mDownX = (int)event.getX();
mDownY = (int)event.getY();
mActivePointerId = event.getPointerId(0);
break;
case MotionEvent.ACTION_MOVE:
if (mActivePointerId == INVALID_POINTER_ID) {
break;
}
int pointerIndex = event.findPointerIndex(mActivePointerId);
mLastEventY = (int) event.getY(pointerIndex);
int deltaY = mLastEventY - mDownY;
if (mCellIsMobile) {
mHoverCellCurrentBounds.offsetTo(mHoverCellOriginalBounds.left,
mHoverCellOriginalBounds.top + deltaY + mTotalOffset);
mHoverCell.setBounds(mHoverCellCurrentBounds);
invalidate();
handleCellSwitch();
mIsMobileScrolling = false;
handleMobileCellScroll();
return false;
}
break;
case MotionEvent.ACTION_UP:
touchEventsEnded();
break;
case MotionEvent.ACTION_CANCEL:
touchEventsCancelled();
break;
case MotionEvent.ACTION_POINTER_UP:
/* If a multitouch event took place and the original touch dictating
* the movement of the hover cell has ended, then the dragging event
* ends and the hover cell is animated to its corresponding position
* in the listview. */
pointerIndex = (event.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >>
MotionEvent.ACTION_POINTER_INDEX_SHIFT;
final int pointerId = event.getPointerId(pointerIndex);
if (pointerId == mActivePointerId) {
touchEventsEnded();
}
break;
default:
break;
}
return super.onTouchEvent(event);
}
/**
* This method determines whether the hover cell has been shifted far enough
* to invoke a cell swap. If so, then the respective cell swap candidate is
* determined and the data set is changed. Upon posting a notification of the
* data set change, a layout is invoked to place the cells in the right place.
* Using a ViewTreeObserver and a corresponding OnPreDrawListener, we can
* offset the cell being swapped to where it previously was and then animate it to
* its new position.
*/
private void handleCellSwitch() {
final int deltaY = mLastEventY - mDownY;
int deltaYTotal = mHoverCellOriginalBounds.top + mTotalOffset + deltaY;
View belowView = getViewForID(mBelowItemId);
View mobileView = getViewForID(mMobileItemId);
View aboveView = getViewForID(mAboveItemId);
boolean isBelow = (belowView != null) && (deltaYTotal > belowView.getTop());
boolean isAbove = (aboveView != null) && (deltaYTotal < aboveView.getTop());
if (isBelow || isAbove) {
final long switchItemID = isBelow ? mBelowItemId : mAboveItemId;
View switchView = isBelow ? belowView : aboveView;
final int originalItem = getPositionForView(mobileView);
if (switchView == null) {
updateNeighborViewsForID(mMobileItemId);
return;
}
swapElements(mCheeseList, originalItem, getPositionForView(switchView));
((BaseAdapter) getAdapter()).notifyDataSetChanged();
mDownY = mLastEventY;
final int switchViewStartTop = switchView.getTop();
if (android.os.Build.VERSION.SDK_INT <= android.os.Build.VERSION_CODES.KITKAT){
mobileView.setVisibility(View.VISIBLE);
switchView.setVisibility(View.INVISIBLE);
} else{
mobileView.setVisibility(View.INVISIBLE);
switchView.setVisibility(View.VISIBLE);
}
updateNeighborViewsForID(mMobileItemId);
final ViewTreeObserver observer = getViewTreeObserver();
observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
public boolean onPreDraw() {
observer.removeOnPreDrawListener(this);
View switchView = getViewForID(switchItemID);
mTotalOffset += deltaY;
int switchViewNewTop = switchView.getTop();
int delta = switchViewStartTop - switchViewNewTop;
switchView.setTranslationY(delta);
ObjectAnimator animator = ObjectAnimator.ofFloat(switchView,
View.TRANSLATION_Y, 0);
animator.setDuration(MOVE_DURATION);
animator.start();
return true;
}
});
}
}
private void swapElements(ArrayList arrayList, int indexOne, int indexTwo) {
Object temp = arrayList.get(indexOne);
arrayList.set(indexOne, arrayList.get(indexTwo));
arrayList.set(indexTwo, temp);
}
/**
* Resets all the appropriate fields to a default state while also animating
* the hover cell back to its correct location.
*/
private void touchEventsEnded () {
final View mobileView = getViewForID(mMobileItemId);
if (mCellIsMobile|| mIsWaitingForScrollFinish) {
mCellIsMobile = false;
mIsWaitingForScrollFinish = false;
mIsMobileScrolling = false;
mActivePointerId = INVALID_POINTER_ID;
// If the autoscroller has not completed scrolling, we need to wait for it to
// finish in order to determine the final location of where the hover cell
// should be animated to.
if (mScrollState != OnScrollListener.SCROLL_STATE_IDLE) {
mIsWaitingForScrollFinish = true;
return;
}
mHoverCellCurrentBounds.offsetTo(mHoverCellOriginalBounds.left, mobileView.getTop());
ObjectAnimator hoverViewAnimator = ObjectAnimator.ofObject(mHoverCell, "bounds",
sBoundEvaluator, mHoverCellCurrentBounds);
hoverViewAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
invalidate();
}
});
hoverViewAnimator.addListener(new AnimatorListenerAdapter() {
#Override
public void onAnimationStart(Animator animation) {
setEnabled(false);
}
#Override
public void onAnimationEnd(Animator animation) {
mAboveItemId = INVALID_ID;
mMobileItemId = INVALID_ID;
mBelowItemId = INVALID_ID;
mobileView.setVisibility(VISIBLE);
mHoverCell = null;
setEnabled(true);
invalidate();
}
});
hoverViewAnimator.start();
} else {
touchEventsCancelled();
}
}
/**
* Resets all the appropriate fields to a default state.
*/
private void touchEventsCancelled () {
View mobileView = getViewForID(mMobileItemId);
if (mCellIsMobile) {
mAboveItemId = INVALID_ID;
mMobileItemId = INVALID_ID;
mBelowItemId = INVALID_ID;
mobileView.setVisibility(VISIBLE);
mHoverCell = null;
invalidate();
}
mCellIsMobile = false;
mIsMobileScrolling = false;
mActivePointerId = INVALID_POINTER_ID;
}
/**
* This TypeEvaluator is used to animate the BitmapDrawable back to its
* final location when the user lifts his finger by modifying the
* BitmapDrawable's bounds.
*/
private final static TypeEvaluator<Rect> sBoundEvaluator = new TypeEvaluator<Rect>() {
public Rect evaluate(float fraction, Rect startValue, Rect endValue) {
return new Rect(interpolate(startValue.left, endValue.left, fraction),
interpolate(startValue.top, endValue.top, fraction),
interpolate(startValue.right, endValue.right, fraction),
interpolate(startValue.bottom, endValue.bottom, fraction));
}
public int interpolate(int start, int end, float fraction) {
return (int)(start + fraction * (end - start));
}
};
/**
* Determines whether this listview is in a scrolling state invoked
* by the fact that the hover cell is out of the bounds of the listview;
*/
private void handleMobileCellScroll() {
mIsMobileScrolling = handleMobileCellScroll(mHoverCellCurrentBounds);
}
/**
* This method is in charge of determining if the hover cell is above
* or below the bounds of the listview. If so, the listview does an appropriate
* upward or downward smooth scroll so as to reveal new mItems.
*/
public boolean handleMobileCellScroll(Rect r) {
int offset = computeVerticalScrollOffset();
int height = getHeight();
int extent = computeVerticalScrollExtent();
int range = computeVerticalScrollRange();
int hoverViewTop = r.top;
int hoverHeight = r.height();
if (hoverViewTop <= 0 && offset > 0) {
smoothScrollBy(-mSmoothScrollAmountAtEdge, 0);
return true;
}
if (hoverViewTop + hoverHeight >= height && (offset + extent) < range) {
smoothScrollBy(mSmoothScrollAmountAtEdge, 0);
return true;
}
return false;
}
public void setCheeseList(ArrayList<String> cheeseList) {
mCheeseList = cheeseList;
}
/**
* This scroll listener is added to the listview in order to handle cell swapping
* when the cell is either at the top or bottom edge of the listview. If the hover
* cell is at either edge of the listview, the listview will begin scrolling. As
* scrolling takes place, the listview continuously checks if new cells became visible
* and determines whether they are potential candidates for a cell swap.
*/
private OnScrollListener mScrollListener = new OnScrollListener () {
private int mPreviousFirstVisibleItem = -1;
private int mPreviousVisibleItemCount = -1;
private int mCurrentFirstVisibleItem;
private int mCurrentVisibleItemCount;
private int mCurrentScrollState;
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
int totalItemCount) {
mCurrentFirstVisibleItem = firstVisibleItem;
mCurrentVisibleItemCount = visibleItemCount;
mPreviousFirstVisibleItem = (mPreviousFirstVisibleItem == -1) ? mCurrentFirstVisibleItem
: mPreviousFirstVisibleItem;
mPreviousVisibleItemCount = (mPreviousVisibleItemCount == -1) ? mCurrentVisibleItemCount
: mPreviousVisibleItemCount;
checkAndHandleFirstVisibleCellChange();
checkAndHandleLastVisibleCellChange();
mPreviousFirstVisibleItem = mCurrentFirstVisibleItem;
mPreviousVisibleItemCount = mCurrentVisibleItemCount;
}
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
mCurrentScrollState = scrollState;
mScrollState = scrollState;
isScrollCompleted();
}
/**
* This method is in charge of invoking 1 of 2 actions. Firstly, if the listview
* is in a state of scrolling invoked by the hover cell being outside the bounds
* of the listview, then this scrolling event is continued. Secondly, if the hover
* cell has already been released, this invokes the animation for the hover cell
* to return to its correct position after the listview has entered an idle scroll
* state.
*/
private void isScrollCompleted() {
if (mCurrentVisibleItemCount > 0 && mCurrentScrollState == SCROLL_STATE_IDLE) {
if (mCellIsMobile && mIsMobileScrolling) {
handleMobileCellScroll();
} else if (mIsWaitingForScrollFinish) {
touchEventsEnded();
}
}
}
/**
* Determines if the listview scrolled up enough to reveal a new cell at the
* top of the list. If so, then the appropriate parameters are updated.
*/
public void checkAndHandleFirstVisibleCellChange() {
if (mCurrentFirstVisibleItem != mPreviousFirstVisibleItem) {
if (mCellIsMobile && mMobileItemId != INVALID_ID) {
updateNeighborViewsForID(mMobileItemId);
handleCellSwitch();
}
}
}
/**
* Determines if the listview scrolled down enough to reveal a new cell at the
* bottom of the list. If so, then the appropriate parameters are updated.
*/
public void checkAndHandleLastVisibleCellChange() {
int currentLastVisibleItem = mCurrentFirstVisibleItem + mCurrentVisibleItemCount;
int previousLastVisibleItem = mPreviousFirstVisibleItem + mPreviousVisibleItemCount;
if (currentLastVisibleItem != previousLastVisibleItem) {
if (mCellIsMobile && mMobileItemId != INVALID_ID) {
updateNeighborViewsForID(mMobileItemId);
handleCellSwitch();
}
}
}
};
}
Ran out of characters here is NoteActivity.java on pastebin.com: http://pastebin.com/MgaFXHb6
If you need to see any other code let me know.

Animating list items that are partially hidden or at the end of the currently visible views in a ListView

I'm currently implementing my own custom listview. I idea is that a longclick on a list item will scale the surrounding visible views to a smaller value scale and leave the the current view normal, whilst expanding a view below the current selected view. Once the user touches anywhere around the selected view, it will scale everything back to normal.
Unfortunately, there's an issue whereby the last view maybe partially hidden during the scale animation and so doesn't scale back to normal. So when you try to gain focus to the rest of the Listview by touching outside the selected list item, it remains a smaller scale.
I've tried a few approaches, from using ViewTreeObserver to storing the positions of the list items to make sure that they get scaled, but nothing has worked. Below is my custom listview:
public class ZoomListView extends ListView implements AdapterView.OnItemLongClickListener {
private static final String TAG = ZoomListView.class.getName();
private int _xPos;
private int _yPos;
private int _pointerId;
private Rect _viewBounds;
private boolean _isZoomed;
private OnItemClickListener _listner;
private OnItemFocused _onItemFocusedLis;
private int _expandingViewHeight = 0;
private int _previousFocusedViewHeight;
public interface OnItemFocused {
/**
* This interface can be used to be notified when a particular item should be disabled, or is currently not focused
* #param position
*/
public void onItemOutOfFocus(int position, boolean status_);
public View onItemFocused(View focusedView_, int listViewPosition_, long uniqueId_);
}
public ZoomListView(Context context_) {
super(context_);
init(context_);
}
public ZoomListView(Context context_, AttributeSet attrs) {
super(context_, attrs);
init(context_);
}
public ZoomListView(Context context_, AttributeSet attrs, int defStyle) {
super(context_, attrs, defStyle);
init(context_);
}
#Override
public void setOnItemClickListener(OnItemClickListener listener) {
if(!(listener == null)){
_listner = listener;
}
super.setOnItemClickListener(listener);
}
private void init(Context context_){
setOnItemLongClickListener(this);
}
public void setOnItemDisableListener(OnItemFocused listener_){
_onItemFocusedLis = listener_;
}
private void scaleChildViews(long rowId_, int itemPos_, float scale, boolean shouldEnable){
if (_isZoomed) {
getParent().requestDisallowInterceptTouchEvent(true);
}
int firstVisiblePosition = getFirstVisiblePosition();
int pos = pointToPosition(_xPos, _yPos);
int positionOrg = pos - firstVisiblePosition;
scaleAllVisibleViews(positionOrg, scale, shouldEnable);
}
private void scaleAllVisibleViews(final int clickedItemPosition_, final float scale_, final boolean shouldEnable_) {
Animation scaleAnimation;
if(_isZoomed){
scaleAnimation = getZoomAnimation(1f, 0.8f, 1f, 0.8f);
}else{
scaleAnimation = getZoomAnimation(0.8f, 1f, 0.8f, 1f);
}
int firstVisiblePosition = getFirstVisiblePosition();
int count = getChildCount();
for (int i = 0; i < count; i++) {
int pos = i;
if (_isZoomed) {
if (getAdapter().getItemId(clickedItemPosition_) != getAdapter().getItemId(pos)) {
scaleView(pos, scale_, shouldEnable_, scaleAnimation);
}else{
displayExpandingView(pos, clickedItemPosition_);
}
} else {
View view = getChildAt(pos);
View viewToShow = _onItemFocusedLis.onItemFocused(view, pos, getAdapter().getItemId(clickedItemPosition_));
if(viewToShow != null){
viewToShow.setVisibility(GONE);
}
scaleView(pos, scale_, shouldEnable_, scaleAnimation);
}
}
}
private void displayExpandingView(int position_, int clickedItemPosition_){
View view = getChildAt(position_);
if(view != null){
Log.v(TAG, "view is valid");
View viewToShow = _onItemFocusedLis.onItemFocused(view, position_, getAdapter().getItemId(clickedItemPosition_));
viewToShow.setVisibility(VISIBLE);
Animation flip = new CyclicFlipAnimation(50f);
flip.setDuration(500);
viewToShow.startAnimation(flip);
if(_expandingViewHeight <= 0){
viewToShow.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
_expandingViewHeight = viewToShow.getMeasuredHeight();
Log.v(TAG, "expanding view hieght is + " + _expandingViewHeight);
}
}
}
private Animation getZoomAnimation(float fromX_, float toX_, float fromY_, float toY_){
Animation scaleAnimation = new ScaleAnimation(
fromX_, toX_,
fromY_, toY_,
Animation.RELATIVE_TO_SELF, 0.5f,
Animation.RELATIVE_TO_SELF, 0.5f);
scaleAnimation.setFillAfter(true);
scaleAnimation.setDuration(500);
return scaleAnimation;
}
private void scaleView(int position_, float scale_, boolean shouldEnable_, Animation animation_){
View view = getChildAt(position_);
if (view != null) {
view.startAnimation(animation_);
if (_onItemFocusedLis != null) {
_onItemFocusedLis.onItemOutOfFocus(position_, shouldEnable_);
}
}
}
#Override
public boolean onItemLongClick(AdapterView<?> adapterView, View view, int i, long l) {
_isZoomed = true;
scaleChildViews(l, i, 0.8f, false);
return true;
}
#Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
_xPos = (int) event.getX();
_yPos = (int) event.getY();
_pointerId = event.getPointerId(0);
if (_isZoomed) {
if (!_viewBounds.contains(_xPos, _yPos)) {
_isZoomed = false;
scaleChildViews(1, 1, 1f, true);
}
return false;
}
int position = pointToPosition(_xPos, _yPos);
int childNum = (position != INVALID_POSITION) ? position - getFirstVisiblePosition() : -1;
View itemView = (childNum >= 0) ? getChildAt(childNum) : null;
if (itemView != null) {
_viewBounds = getChildViewRect(this, itemView);
}
break;
}
return super.onTouchEvent(event);
}
private Rect getChildViewRect(View parentView, View childView) {
final Rect childRect = new Rect(childView.getLeft(), childView.getTop(), childView.getRight(), childView.getBottom() + _expandingViewHeight);
if (parentView == childView) {
return childRect;
}
ViewGroup parent = (ViewGroup) childView.getParent();
while (parent != parentView) {
childRect.offset(parent.getLeft(), parent.getTop());
childView = parent;
parent = (ViewGroup) childView.getParent();
}
return childRect;
}
}
Any idea's on what maybe wrong, thanks.
To fix this issue I ended up adding a ACTION_MOVE case statement in my onTouch method. So as long as the list items were focused again, when the list performed a move i.e. scrolled the list item, the remaining list items are restored to their original size. To achieve this you need to maintain a map/collection of id's that correspond to your list items and iterate over them once the user scrolls.
Heres the full somwhat functiong implementation:
package com.sun.tweetfiltrr.zoomlistview;
import android.content.Context;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.Animation;
import android.view.animation.ScaleAnimation;
import android.widget.AdapterView;
import android.widget.ListAdapter;
import android.widget.ListView;
import com.sun.tweetfiltrr.animation.CyclicFlipAnimation;
import java.util.HashMap;
import java.util.Map;
/**
*/
public class ZoomListView extends ListView implements AdapterView.OnItemLongClickListener {
private static final String TAG = ZoomListView.class.getName();
private int _xPos;
private int _yPos;
private int _pointerId;
private Rect _viewBounds;
private boolean _isZoomed;
private OnItemClickListener _listner;
private OnItemFocused _onItemFocusedLis;
private int _expandingViewHeight = 0;
private int _previousFocusedViewHeight;
private OnScrollListener _onScrollListener;
private boolean _shouldPerformScrollAnimation;
final private Map<Long, PropertyHolder> _itemIDToProperty = new HashMap<Long, PropertyHolder>();
private long _currentFocusedId;
public interface OnItemFocused {
/**
* This interface can be used to be notified when a particular item should be disabled, or is currently not focused
* #param position
*/
public void onItemScaleOut(int position, View view, boolean status_);
public void onItemRestore(int position, View view, boolean status_);
public View onItemFocused(View focusedView_, int listViewPosition_, long uniqueId_);
}
public ZoomListView(Context context_) {
super(context_);
init(context_);
}
public ZoomListView(Context context_, AttributeSet attrs) {
super(context_, attrs);
init(context_);
}
public ZoomListView(Context context_, AttributeSet attrs, int defStyle) {
super(context_, attrs, defStyle);
init(context_);
}
private void init(Context context_){
setOnItemLongClickListener(this);
}
public void setOnItemDisableListener(OnItemFocused listener_){
_onItemFocusedLis = listener_;
}
private void scaleChildViews(long rowId_, int itemPos_, float scale, boolean shouldEnable){
if (_isZoomed) {
getParent().requestDisallowInterceptTouchEvent(true);
}
scaleAllVisibleViews(shouldEnable);
}
private void scaleAllVisibleViews(final boolean shouldEnable_) {
final ListAdapter adapter = getAdapter();
Animation scaleAnimation;
if(_isZoomed){
scaleAnimation = getZoomAnimation(1f, 0.6f, 1f, 0.6f);
}else{
scaleAnimation = getZoomAnimation(0.6f, 1f, 0.6f, 1f);
}
int count = getChildCount();
for (int i = 0; i < count; i++) {
applyAnimation(i, adapter, shouldEnable_, scaleAnimation);
}
}
private void applyAnimation(int position_, ListAdapter adapter_, boolean shouldEnable_, Animation animation_){
if (_isZoomed) {
if (_currentFocusedId != adapter_.getItemId(position_)) {
scaleView(position_, shouldEnable_, animation_);
}else{
displayExpandingView(position_, adapter_);
}
}else{
if(_currentFocusedId != getAdapter().getItemId(position_)){
scaleView(position_, shouldEnable_, animation_);
}else{
View view = getChildAt(position_);
View viewToShow = _onItemFocusedLis.onItemFocused(view, position_, getAdapter().getItemId(position_));
if(viewToShow != null){
viewToShow.setVisibility(GONE);
}
}
_itemIDToProperty.remove(getAdapter().getItemId(position_));
}
}
private void displayExpandingView(int position_, ListAdapter adapter_){
View view = getChildAt(position_);
if(view != null){
View viewToShow = _onItemFocusedLis.onItemFocused(view, position_, adapter_.getItemId(position_));
viewToShow.setVisibility(VISIBLE);
Animation flip = new CyclicFlipAnimation(60f);
flip.setDuration(1000);
viewToShow.startAnimation(flip);
if(_expandingViewHeight <= 0){
viewToShow.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
_expandingViewHeight = viewToShow.getMeasuredHeight();
}
}
}
private Animation getZoomAnimation(float fromX_, float toX_, float fromY_, float toY_){
Animation scaleAnimation = new ScaleAnimation(
fromX_, toX_,
fromY_, toY_,
Animation.RELATIVE_TO_SELF, 0.5f,
Animation.ABSOLUTE, 0.7f);
scaleAnimation.setFillAfter(true);
scaleAnimation.setDuration(500);
return scaleAnimation;
}
private void scaleView(int position_, boolean shouldEnable_, Animation animationScale_ ){
View view = getChildAt(position_);
ListAdapter adapter = getAdapter();
long id = adapter.getItemId(position_);
view.startAnimation(animationScale_);
PropertyHolder holder = _itemIDToProperty.get(id);
if (holder == null) {
holder = new PropertyHolder((int) view.getTop(), (int) (view.getBottom()));
_itemIDToProperty.put(id, holder);
}
int h = view.getHeight();
if (_isZoomed) {
view.animate().translationYBy((h * 0.5f)).setDuration(500).start();
if (_onItemFocusedLis != null) {
_onItemFocusedLis.onItemScaleOut(position_, view, shouldEnable_);
}
} else {
view.animate().translationYBy(-(h * 0.5f )).setDuration(500).start();
if (_onItemFocusedLis != null) {
_onItemFocusedLis.onItemRestore(position_, view, shouldEnable_);
}
}
}
#Override
public boolean onItemLongClick(AdapterView<?> adapterView, View view, int i, long l) {
_isZoomed = true;
int firstVisiblePosition = getFirstVisiblePosition();
int pos = pointToPosition(_xPos, _yPos);
int positionOrg = pos - firstVisiblePosition;
_currentFocusedId = getAdapter().getItemId(positionOrg);
scaleChildViews(l, i, 0.8f, false);
return true;
}
#Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
_xPos = (int) event.getX();
_yPos = (int) event.getY();
_pointerId = event.getPointerId(0);
if (_isZoomed) {
if (!_viewBounds.contains(_xPos, _yPos)) {
_isZoomed = false;
scaleChildViews(1, 1, 1f, true);
}
return false;
}
int position = pointToPosition(_xPos, _yPos);
int childNum = (position != INVALID_POSITION) ? position - getFirstVisiblePosition() : -1;
View itemView = (childNum >= 0) ? getChildAt(childNum) : null;
if (itemView != null) {
_viewBounds = getChildViewRect(this, itemView);
}
break;
case MotionEvent.ACTION_MOVE:
if (!_isZoomed) {
animateRemaining();
}
break;
}
return super.onTouchEvent(event);
}
private void animateRemaining(){
if(!_itemIDToProperty.isEmpty()){
for(int i = 0; i < getChildCount(); i++){
long id = getAdapter().getItemId(i);
PropertyHolder n = _itemIDToProperty.get(id);
if(n != null){
if(_currentFocusedId != getAdapter().getItemId(i)){
scaleView(i, true, getZoomAnimation(0.6f, 1, 0.6f, 1f));
}else{
Log.v(TAG, "not translating for " + getAdapter().getItemId(i)+ " for id " + _currentFocusedId);
}
_itemIDToProperty.remove(id);
}
}
}
}
private Rect getChildViewRect(View parentView, View childView) {
final Rect childRect = new Rect(childView.getLeft(), childView.getTop(), childView.getRight(), childView.getBottom() + _expandingViewHeight);
if (parentView == childView) {
return childRect;
}
ViewGroup parent = (ViewGroup) childView.getParent();
while (parent != parentView) {
childRect.offset(parent.getLeft(), parent.getTop());
childView = parent;
parent = (ViewGroup) childView.getParent();
}
return childRect;
}
private class PropertyHolder{
int _top;
int _bot;
private PropertyHolder(int top_, int bot_){
_top = top_;
_bot = bot_;
}
}
}
It's far from perfect but its a starting point.

Categories

Resources