I'm trying to make a customview that displays multiple images into one view with Fresco. I've read the docs and went through some steps but my implementation doesn't seem to work. Here's the code:
public class MultiDraweeView extends View {
private static final int MAX_COUNT = 4;
private MultiDraweeHolder<GenericDraweeHierarchy> multiDraweeHolder;
private ArrayList<GenericDraweeHierarchy> hierarchies;
public MultiDraweeView(Context context) {
super(context);
init();
}
public MultiDraweeView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public MultiDraweeView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
multiDraweeHolder = new MultiDraweeHolder<>();
hierarchies = new ArrayList<>();
for (int i = 0; i < MAX_COUNT; i++) {
hierarchies.add(new GenericDraweeHierarchyBuilder(getResources())
.build());
}
}
public void setImageUris(Uri... uris) {
multiDraweeHolder.clear();
for (int i = 0; i < (uris.length < MAX_COUNT ? uris.length : MAX_COUNT); i++) {
final int j = i;
final DraweeHolder<GenericDraweeHierarchy> draweeHolder = DraweeHolder.create(hierarchies.get(i), getContext());
DraweeController controller = Fresco.newDraweeControllerBuilder()
.setUri(Uri.parse("http://www.thewrap.com/wp-content/uploads/2015/11/Donald-Trump.jpg"))
.setOldController(draweeHolder.getController())
.setControllerListener(new ControllerListener<ImageInfo>() {
#Override
public void onSubmit(String id, Object callerContext) {
Log.e("fresco" + j, "onSubmit");
}
#Override
public void onFinalImageSet(String id, ImageInfo imageInfo, Animatable animatable) {
Log.e("fresco" + j, "onFinalImageset");
invalidate();
}
#Override
public void onIntermediateImageSet(String id, ImageInfo imageInfo) {
Log.e("fresco" + j, "onIntermediateImageSet");
}
#Override
public void onIntermediateImageFailed(String id, Throwable throwable) {
Log.e("fresco" + j, "onIntermediateImageFailed");
}
#Override
public void onFailure(String id, Throwable throwable) {
Log.e("fresco" + j, "onFailure");
}
#Override
public void onRelease(String id) {
Log.e("fresco" + j, "onRelease");
}
})
.build();
draweeHolder.setController(controller);
multiDraweeHolder.add(draweeHolder);
}
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Log.e("fresco", "ondraw");
for (int i = 0; i < multiDraweeHolder.size(); i++) {
Drawable drawable = multiDraweeHolder.get(i).getTopLevelDrawable();
drawable.setBounds(50 * i, 50 * i, 50 * (i + 1), 50 * (i + 1));
drawable.draw(canvas);
}
}
#Override
protected boolean verifyDrawable(#NonNull Drawable who) {
return multiDraweeHolder.verifyDrawable(who);
}
#Override
public void onDetachedFromWindow() {
super.onDetachedFromWindow();
multiDraweeHolder.onDetach();
}
#Override
public void onStartTemporaryDetach() {
super.onStartTemporaryDetach();
multiDraweeHolder.onDetach();
}
#Override
public void onAttachedToWindow() {
super.onAttachedToWindow();
multiDraweeHolder.onAttach();
}
#Override
public void onFinishTemporaryDetach() {
super.onFinishTemporaryDetach();
multiDraweeHolder.onAttach();
}
}
The logic behind my code is to invalidate the view as soon as the image downloads inonFinalImageSetso it redraws. But the Drawables are not drawing. If then i send the app to the background and open it again so onStart is called again, the Drawables show. Am I missing something here, or is there any example of a custom DraweView that extends View and not ImageView?
You need to set the Drawable.Callback as described here: http://frescolib.org/docs/writing-custom-views.html
Also, make sure that the callback actually invalidates the correct are. If you invalidate the wrong one, it won't work either.
Related
I have custom view (extends LinearLayout) which contains RecyclerView. When I add new items RecyclerView doesn't change size. I think problem is that my custom view doesn`t give enough space to RecyclerView.
The question: How to change heigh of custom view depends on chlid recylerview size?
If I'm wrong, then correct me please
CustomView:
public class ExpandableView extends LinearLayout {
private Settings mSettings ;
private int mExpandState;
private ValueAnimator mExpandAnimator;
private ValueAnimator mParentAnimator;
private AnimatorSet mExpandScrollAnimatorSet;
private int mExpandedViewHeight;
private boolean mIsInit = true;
private boolean isAllowedExpand = false;
private ScrolledParent mScrolledParent;
private OnExpandListener mOnExpandListener;
public ExpandableView(Context context) {
super(context);
init(null);
}
public ExpandableView(Context context, #Nullable AttributeSet attrs) {
super(context, attrs);
init(attrs);
}
public ExpandableView(Context context, #Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
private void init(AttributeSet attrs) {
setOrientation(VERTICAL);
this.setClipChildren(false);
this.setClipToPadding(false);
mExpandState = ExpandState.PRE_INIT;
mSettings = new Settings();
if(attrs!=null) {
TypedArray typedArray = getContext().obtainStyledAttributes(attrs, R.styleable.ExpandableView);
mSettings.expandDuration = typedArray.getInt(R.styleable.ExpandableView_expDuration, Settings.EXPAND_DURATION);
mSettings.expandWithParentScroll = typedArray.getBoolean(R.styleable.ExpandableView_expWithParentScroll,false);
mSettings.expandScrollTogether = typedArray.getBoolean(R.styleable.ExpandableView_expExpandScrollTogether,true);
typedArray.recycle();
}
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int childCount = getChildCount();
if(childCount!=2) {
throw new IllegalStateException("ExpandableLayout must has two child view !");
}
if(mIsInit) {
((MarginLayoutParams)getChildAt(0).getLayoutParams()).bottomMargin=0;
MarginLayoutParams marginLayoutParams = ((MarginLayoutParams)getChildAt(1).getLayoutParams());
marginLayoutParams.bottomMargin=0;
marginLayoutParams.topMargin=0;
marginLayoutParams.height = 0;
mExpandedViewHeight = getChildAt(1).getMeasuredHeight();
mIsInit =false;
mExpandState = ExpandState.CLOSED;
View view = getChildAt(0);
if (view != null){
view.setOnClickListener(v -> toggle());
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
if(mSettings.expandWithParentScroll) {
mScrolledParent = Utils.getScrolledParent(this);
}
}
private int getParentScrollDistance () {
int distance = 0;
if(mScrolledParent == null) {
return distance;
}
distance = (int) (getY() + getMeasuredHeight() + mExpandedViewHeight - mScrolledParent.scrolledView.getMeasuredHeight());
for(int index = 0; index < mScrolledParent.childBetweenParentCount; index++) {
ViewGroup parent = (ViewGroup) getParent();
distance+=parent.getY();
}
return distance;
}
private void verticalAnimate(final int startHeight, final int endHeight ) {
int distance = getParentScrollDistance();
final View target = getChildAt(1);
mExpandAnimator = ValueAnimator.ofInt(startHeight,endHeight);
mExpandAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator animation) {
target.getLayoutParams().height = (int) animation.getAnimatedValue();
target.requestLayout();
}
});
mExpandAnimator.addListener(new AnimatorListenerAdapter() {
#Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
if(endHeight-startHeight < 0) {
mExpandState = ExpandState.CLOSED;
if (mOnExpandListener != null) {
mOnExpandListener.onExpand(false);
}
} else {
mExpandState=ExpandState.EXPANDED;
if(mOnExpandListener != null) {
mOnExpandListener.onExpand(true);
}
}
}
});
//todo ??????????????????????
mExpandState=mExpandState==ExpandState.EXPANDED?ExpandState.CLOSING :ExpandState.EXPANDING;
mExpandAnimator.setDuration(mSettings.expandDuration);
if(mExpandState == ExpandState.EXPANDING && mSettings.expandWithParentScroll && distance > 0) {
mParentAnimator = Utils.createParentAnimator(mScrolledParent.scrolledView, distance, mSettings.expandDuration);
mExpandScrollAnimatorSet = new AnimatorSet();
if(mSettings.expandScrollTogether) {
mExpandScrollAnimatorSet.playTogether(mExpandAnimator,mParentAnimator);
} else {
mExpandScrollAnimatorSet.playSequentially(mExpandAnimator,mParentAnimator);
}
mExpandScrollAnimatorSet.start();
} else {
mExpandAnimator.start();
}
}
public void setExpand(boolean expand) {
if (mExpandState == ExpandState.PRE_INIT) {return;}
getChildAt(1).getLayoutParams().height=expand?mExpandedViewHeight:0;
requestLayout();
mExpandState=expand?ExpandState.EXPANDED:ExpandState.CLOSED;
}
public boolean isExpanded() {
return mExpandState==ExpandState.EXPANDED;
}
public void toggle() {
if (isAllowedExpand){
if(mExpandState==ExpandState.EXPANDED) {
close();
}else if(mExpandState==ExpandState.CLOSED) {
expand();
}
}
}
public void expand() {
verticalAnimate(0,mExpandedViewHeight);
}
public void close() {
verticalAnimate(mExpandedViewHeight,0);
}
public interface OnExpandListener {
void onExpand(boolean expanded) ;
}
public void setOnExpandListener(OnExpandListener onExpandListener) {
this.mOnExpandListener = onExpandListener;
}
public void setExpandScrollTogether(boolean expandScrollTogether) {
this.mSettings.expandScrollTogether = expandScrollTogether;
}
public void setExpandWithParentScroll(boolean expandWithParentScroll) {
this.mSettings.expandWithParentScroll = expandWithParentScroll;
}
public void setExpandDuration(int expandDuration) {
this.mSettings.expandDuration = expandDuration;
}
#Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
if(mExpandAnimator!=null&&mExpandAnimator.isRunning()) {
mExpandAnimator.cancel();
mExpandAnimator.removeAllUpdateListeners();
}
if(mParentAnimator!=null&&mParentAnimator.isRunning()) {
mParentAnimator.cancel();
mParentAnimator.removeAllUpdateListeners();
}
if(mExpandScrollAnimatorSet!=null) {
mExpandScrollAnimatorSet.cancel();
}
}
public void setAllowedExpand(boolean allowedExpand) {
isAllowedExpand = allowedExpand;
}
public void refreshView(){
ViewGroup.LayoutParams params = this.getLayoutParams();
Log.d("tag", "params - " + params.height);
}
}
Specify your item height inside RecyclerView Adapter like below :
LinearLayout.LayoutParams relParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT);
relParams.height = 100;
relParams.width = Utility.getScreenWidth(mContext);
holder.yourDesireView.setLayoutParams(relParams);
Here is the screen width calculator method :
public static int getScreenWidth(Context context) {
if (context == null) {
return 0;
}
DisplayMetrics metrics = context.getResources().getDisplayMetrics();
return metrics.widthPixels;
}
I think it will be helpful...
I want to create a animation in android with Gridview. The animation will be when I will change the number of columns from 2 to 4.
I used the following line to change the number of columns:
If (true)
gridView.setNumColumns(2);
Else
gridView.setNumColumns(4);
I want to achieve animation like this:
https://www.youtube.com/watch?v=1NkuChdWA_I
I need it too and then i created the following class:
/**
* Created by Butzke on 19/05/2017.
*/
public class GridViewAnimated extends GridView {
private int animationDuration = 300, nextColumns;
private boolean animating = false, waitingScroll, shouldWait = true;
private GridViewAnimationListener animationListener;
public GridViewAnimated(Context context) {
super(context);
init();
}
public GridViewAnimated(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public GridViewAnimated(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
#TargetApi(21)
public GridViewAnimated(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init();
}
#Override
public int computeVerticalScrollOffset() {
return super.computeVerticalScrollOffset();
}
private void init() {
this.setStretchMode(GridView.STRETCH_COLUMN_WIDTH);
}
public void setAnimating(boolean animating) {
this.animating = animating;
}
#Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
if (waitingScroll) {
super.onScrollChanged(l, t, oldl, oldt);
if (computeVerticalScrollOffset() == 0) {
setEnabled(true);
waitingScroll = false;
setAnimating(false);
setNumColumns(nextColumns);
}
} else if (!animating) {
super.onScrollChanged(l, t, oldl, oldt);
}
}
public int getAnimationDuration() {
return animationDuration;
}
public void setAnimationDuration(int animationDuration) {
this.animationDuration = animationDuration;
}
public interface GridViewAnimationListener {
void onAnimationStart(Animator animator);
void onAnimationEnd(Animator animator);
void onAnimationCancel(Animator animator);
void onAnimationRepeat(Animator animator);
}
public GridViewAnimationListener getAnimationListener() {
return animationListener;
}
public void setAnimationListener(GridViewAnimationListener animationListener) {this.animationListener = animationListener;}
public void removeAt(int... positions) {
ArrayList<MyInteger> ints = new ArrayList<>();
for (Integer pos : positions) {
ints.add(new MyInteger(pos));
}
Collections.sort(ints);
int lowest = 99999, highest = 0;
for (MyInteger pos : ints) {
lowest = lowest < pos.getValue() ? lowest : pos.getValue();
highest = highest > pos.getValue() ? highest : pos.getValue();
}
if (lowest >= 0 && highest <= getChildCount()) {
ViewsVO originalViews = getOriginalViews();
for (MyInteger pos : ints) {
getAdapter().removeAt(pos.getValue());
}
getAdapter().notifyDataSetChanged();
animateChildViews(originalViews);
}
}
#Override
public void setAdapter(ListAdapter adapter) {
if (adapter instanceof GridViewAnimatedAdapter) {
super.setAdapter(adapter);
} else {
Log.e("GridViewAnimated", "Adapter needs to be instance of GridViewAnimatedAdapter");
Toast.makeText(getContext(), "Adapter needs to be instance of GridViewAnimatedAdapter", Toast.LENGTH_SHORT).show();
}
}
#Override
public GridViewAnimatedAdapter getAdapter() {
return (GridViewAnimatedAdapter) super.getAdapter();
}
#Override
public void setNumColumns(int numColumns) {
if (getAdapter() == null || !getAdapter().hasStableIds() || getChildCount() == 0) {
super.setNumColumns(numColumns);
setAnimating(false);
return;
} else if (animating) {
return;
}
setAnimating(true);
if (computeVerticalScrollOffset() > 0) {
waitingScroll = true;
setEnabled(false);
smoothScrollToPosition(0);
nextColumns = numColumns;
return;
}
ViewsVO originalViews = getOriginalViews();
super.setNumColumns(numColumns);
getAdapter().notifyDataSetChanged();
animateChildViews(originalViews);
}
private ViewsVO getOriginalViews() {
ViewsVO originalViews = new ViewsVO();
for (int i = 0; i < getChildCount(); i++) {
originalViews.addView(getChildAt(i));
}
return originalViews;
}
private void animateChildViews(final ViewsVO originalViews) {
final GridViewAnimated gridView = this;
getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
#Override
public boolean onPreDraw() {
gridView.getViewTreeObserver().removeOnPreDrawListener(this);
ViewsVO newViews = new ViewsVO();
if (Build.VERSION.SDK_INT >= 21) {
for (int i = 0, z = getChildCount() - 1; z >= 0; z--, i++) {
gridView.getChildAt(z).setTranslationZ(i);
}
}
boolean hasFirst = false;
View firstView = null, lastView = null;
float firstHeight = 0, lastHeight = 0;
ViewsVO newViewsFirstTime = new ViewsVO();
for (int i = 0; i < getChildCount(); i++) {
View view = getChildAt(i);
ViewVO nView = newViews.addView(view);
ViewVO oView = originalViews.getView(view.getId());
if (oView != null) {
view.setScaleX(getScaleX(oView, nView));
view.setScaleY(getScaleY(oView, nView));
view.setTranslationX(getTranslateX(oView, nView));
view.setTranslationY(getTranslateY(oView, nView));
if (!hasFirst) {
firstView = view;
firstHeight = oView.getHeight();
hasFirst = true;
}
lastView = view;
lastHeight = oView.getHeight();
animateView(view);
} else {
newViewsFirstTime.addView(nView);
}
}
for (int i = 0; i < newViewsFirstTime.size(); i++) {
try {
View view = newViewsFirstTime.getViews().get(i).getView();
view.getId();
view.setScaleX(view.getId() > firstView.getId() ? firstView.getScaleX() : lastView.getScaleX());
view.setScaleY(view.getId() > firstView.getId() ? firstView.getScaleY() : lastView.getScaleX());
view.setTranslationX(view.getId() > firstView.getId() ? firstView.getTranslationX() : lastView.getTranslationX());
view.setTranslationY(view.getId() > firstView.getId() ? 0 - firstHeight : lastView.getTranslationY() + lastHeight);
animateView(view);
} catch (Exception ex) {
ex.printStackTrace();
}
}
return false;
}
});
}
private void animateView(final View view) {
ViewPropertyAnimator animator = view.animate().setDuration(animationDuration).translationX(0).translationY(0).scaleX(1).scaleY(1).setListener(new Animator.AnimatorListener() {
#Override
public void onAnimationStart(Animator animator) {animationListener.onAnimationStart(animator);}
#Override
public void onAnimationEnd(Animator animator) {
setAnimating(false);
animationListener.onAnimationEnd(animator);
ViewGroup.LayoutParams params = view.getLayoutParams();
params.width = ViewGroup.LayoutParams.MATCH_PARENT;
params.height = ViewGroup.LayoutParams.MATCH_PARENT;
view.setLayoutParams(params);
}
#Override
public void onAnimationCancel(Animator animator) {animationListener.onAnimationCancel(animator);}
#Override
public void onAnimationRepeat(Animator animator) {animationListener.onAnimationRepeat(animator);}
});
if (Build.VERSION.SDK_INT >= 21) {
animator.translationZ(0);
}
}
private float getTranslateX(ViewVO ov, ViewVO nv) {return ov.getPosX() - nv.getPosX() - ((ov.getWidth() < nv.getWidth() ? nv.getWidth() - ov.getWidth() : ov.getWidth() - nv.getWidth()) * (ov.getWidth() < nv.getWidth() ? 0.5f : -0.5f));}
private float getTranslateY(ViewVO ov, ViewVO nv) {return ov.getPosY() - nv.getPosY() - ((ov.getHeight() < nv.getHeight() ? nv.getHeight() - ov.getHeight() : ov.getHeight() - nv.getHeight()) * (ov.getHeight() < nv.getHeight() ? 0.5f : -0.5f));}
private float getScaleY(ViewVO ov, ViewVO nv) {
return ov.getHeight() / nv.getHeight();
}
private float getScaleX(ViewVO ov, ViewVO nv) {
return ov.getWidth() / nv.getWidth();
}
private class ViewsVO {
private ArrayList<ViewVO> views;
private int size() {
return views.size();
}
private ViewsVO() {
views = new ArrayList<>();
}
private ViewVO addView(View view) {
ViewVO v = new ViewVO(view);
addView(v);
return v;
}
private void addView(ViewVO view) {
views.add(view);
}
private ViewVO getView(long id) {
for (ViewVO view : views) {
if (view.getId() == id) {
return view;
}
}
return null;
}
private ArrayList<ViewVO> getViews() {
return views;
}
}
private class ViewVO {
private View view;
private int id;
private float posY;
private float posX;
private float width;
private float height;
private ViewVO(View view) {
this.view = view;
id = view.getId();
posX = view.getLeft();
posY = view.getTop();
width = view.getWidth();
height = view.getHeight();
}
public int getId() {
return id;
}
public View getView() {
return view;
}
private float getWidth() {
return width;
}
private float getHeight() {
return height;
}
private float getPosY() {
return posY;
}
private float getPosX() {
return posX;
}
}
public static class GridViewAnimatedAdapter extends ArrayAdapter {
private List objects;
public GridViewAnimatedAdapter(Context context, int resource, List objects) {
super(context, resource, objects);
this.objects = objects;
}
public void removeAt(int pos) {objects.remove(pos);}
#Override
public boolean hasStableIds() {
return true;
}
public View dealViewToAnimation(View view) {
ViewGroup.LayoutParams params = view.getLayoutParams();
params.width = ViewGroup.LayoutParams.MATCH_PARENT;
params.height = ViewGroup.LayoutParams.MATCH_PARENT;
view.setLayoutParams(params);
return view;
}
}
private class MyInteger implements Comparable<MyInteger> {
private int value;
public MyInteger(int value) {
this.value = value;
}
public int getValue() {
return value;
}
public void setValue(int value) {
this.value = value;
}
#Override
public int compareTo(MyInteger another) {return value > another.getValue() ? -1 : (value == another.getValue() ? 0 : 1);}}
}
And to use it is quite simple,
The adapter of GridView needs to extends GridViewAnimatedAdapter and:
getView(): To set an ID to each view and in the end of the method getView, needs to finish using the method dealViewToAnimation(View view) Ex:
#Override
public View getView(int position, View view, ViewGroup parent) {
...
view.setId(images.getId());
...
return dealViewToAnimation(view);
}
You can also set a listener to animation status:
gridView.setAnimationListener(new GridViewAnimated.GridViewAnimationListener() {
#Override
public void onAnimationStart(Animator animator) {
mListMenu.setVisible(false);
}
#Override
public void onAnimationEnd(Animator animator) {
mListMenu.setVisible(true);
gridView.setAnimating(false);
}
#Override
public void onAnimationCancel(Animator animator) {}
#Override
public void onAnimationRepeat(Animator animator) {
}
});
With this GridView you can animate changes in column number and animate removal of itens...
Animating change of column number:
gridView.setNumColumns(5);
Animating removal of itens:
gridView.removeAt(2, 7, 4);
gridView.removeAt(2);
When more than one, doesn't matter the order, the method will check the order and remove from the highest to lowest, considering that the highest id (that image added to the view) is the first view...
But despite it doesn't have the scale proportion animation like in the GridViewAnimated, it's better with RecyclerView, GridLayoutManager and DefaultItemAnimator, since it changes columns number and doesn't scroll...
GridLayoutmanager gridLayoutManager = new GridLayoutManager(context, 2);
recyclerView.setLayoutManager(gridLayoutManager);
recyclerView.setItemAnimator(new DefaultItemAnimator());
And then when you need to change the columns:
gridLayoutManager.requestSimpleAnimationsInNextLayout();
gridLayoutManager.setSpanCount(newNumberOfColumns);
I have a custom view PasscodeView in my dialog fragment layout.
PasscodeView.java:
public class PasscodeView extends ViewGroup {
EditText mEditText;
int mDigitCount;
private int mDigitWidth;
private int mDigitRadius;
private int mOuterStrokeWidth;
private int mInnerStrokeWidth;
private int mDigitInnerRadius;
private int mDigitSpacing;
private int mDigitElevation;
private int mControlColor;
private int mHighlightedColor;
private int mInnerColor;
private int mInnerBorderColor;
private OnFocusChangeListener mOnFocusChangeListener;
private PasscodeEntryListener mPasscodeEntryListener;
public PasscodeView(Context context) {
this(context, null);
}
public PasscodeView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public PasscodeView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
// Get style information
TypedArray array = getContext().obtainStyledAttributes(attrs, R.styleable.PasscodeView);
mDigitCount = array.getInt(R.styleable.PasscodeView_numDigits, 4);
// Dimensions
DisplayMetrics metrics = getResources().getDisplayMetrics();
mDigitRadius = array.getDimensionPixelSize(R.styleable.PasscodeView_digitRadius,
(int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 16, metrics));
mOuterStrokeWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 2, metrics);
mInnerStrokeWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 1, metrics);
mDigitInnerRadius = mDigitRadius - ((int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 6, metrics));
mDigitWidth = (mDigitRadius + mOuterStrokeWidth) * 2;
mDigitSpacing = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 16, metrics);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
mDigitElevation = array.getDimensionPixelSize(R.styleable.PasscodeView_digitElevation, 0);
}
// Get theme to resolve defaults
Resources.Theme theme = getContext().getTheme();
mControlColor = Color.DKGRAY;
// Text colour, default to android:colorControlNormal from theme
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
TypedValue controlColor = new TypedValue();
theme.resolveAttribute(android.R.attr.colorControlNormal, controlColor, true);
mControlColor = controlColor.resourceId > 0 ? getResources().getColor(controlColor.resourceId) :
controlColor.data;
}
mControlColor = array.getColor(R.styleable.PasscodeView_controlColor, mControlColor);
// Accent colour, default to android:colorAccent from theme
mHighlightedColor = Color.LTGRAY;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
TypedValue accentColor = new TypedValue();
theme.resolveAttribute(R.attr.colorControlHighlight, accentColor, true);
mHighlightedColor = accentColor.resourceId > 0 ? getResources().getColor(accentColor.resourceId) :
accentColor.data;
}
mHighlightedColor = array.getColor(R.styleable.PasscodeView_controlColorActivated, mHighlightedColor);
//color for the inner circle
mInnerColor = Color.CYAN;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
TypedValue innerColor = new TypedValue();
theme.resolveAttribute(android.R.attr.colorPrimary, innerColor, true);
mInnerColor = innerColor.resourceId > 0 ? getResources().getColor(innerColor.resourceId) :
innerColor.data;
}
mInnerColor = array.getColor(R.styleable.PasscodeView_digitColorFilled, mInnerColor);
//color for the inner circle border
mInnerBorderColor = Color.GREEN;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
TypedValue innerBorderColor = new TypedValue();
theme.resolveAttribute(android.R.attr.colorPrimaryDark, innerBorderColor, true);
mInnerBorderColor = innerBorderColor.resourceId > 0 ? getResources().getColor(innerBorderColor.resourceId) :
innerBorderColor.data;
}
mInnerBorderColor = array.getColor(R.styleable.PasscodeView_digitColorBorder, mInnerBorderColor);
// Recycle the typed array
array.recycle();
// Add child views
setupViews();
}
#Override
public boolean shouldDelayChildPressedState() {
return false;
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// Measure children
for (int i = 0; i < getChildCount(); i++) {
getChildAt(i).measure(widthMeasureSpec, heightMeasureSpec);
}
// Calculate the size of the view
int width = (mDigitWidth * mDigitCount) + (mDigitSpacing * (mDigitCount - 1));
setMeasuredDimension(
width + getPaddingLeft() + getPaddingRight() + (mDigitElevation * 2),
mDigitWidth + getPaddingTop() + getPaddingBottom() + (mDigitElevation * 2));
}
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
// Position the child views
for (int i = 0; i < mDigitCount; i++) {
View child = getChildAt(i);
int left = i * mDigitWidth + (i > 0 ? i * mDigitSpacing : 0);
child.layout(
left + getPaddingLeft() + mDigitElevation,
getPaddingTop() + (mDigitElevation / 2),
left + getPaddingLeft() + mDigitElevation + mDigitWidth,
getPaddingTop() + (mDigitElevation / 2) + mDigitWidth);
}
// Add the edit text as a 1px wide view to allow it to focus
getChildAt(mDigitCount).layout(0, 0, 1, getMeasuredHeight());
}
private void setupViews() {
setWillNotDraw(false);
// Add a digit view for each digit
for (int i = 0; i < mDigitCount; i++) {
DigitView digitView = new DigitView(getContext(), i);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
digitView.setElevation(mDigitElevation);
}
addView(digitView);
}
// Add an "invisible" edit text to handle input
mEditText = new EditText(getContext());
mEditText.setBackgroundColor(getResources().getColor(android.R.color.transparent));
mEditText.setTextColor(getResources().getColor(android.R.color.transparent));
mEditText.setCursorVisible(false);
mEditText.setFilters(new InputFilter[]{new InputFilter.LengthFilter(mDigitCount)});
mEditText.setInputType(InputType.TYPE_CLASS_NUMBER);
mEditText.setKeyListener(DigitsKeyListener.getInstance("1234567890"));
mEditText.setImeOptions(EditorInfo.IME_FLAG_NO_EXTRACT_UI);
mEditText.setOnFocusChangeListener(new OnFocusChangeListener() {
#Override
public void onFocusChange(View v, boolean hasFocus) {
// Update the selected state of the views
int length = mEditText.getText().length();
updateChilViewSelectionStates(length, hasFocus);
// Make sure the cursor is at the end
mEditText.setSelection(length);
// Provide focus change events to any listener
if (mOnFocusChangeListener != null) {
mOnFocusChangeListener.onFocusChange(PasscodeView.this, hasFocus);
}
}
});
mEditText.addTextChangedListener(new TextWatcher() {
#Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
#Override
public void afterTextChanged(Editable s) {
int length = s.length();
updateChilViewSelectionStates(length, mEditText.hasFocus());
if (length == mDigitCount && mPasscodeEntryListener != null) {
mPasscodeEntryListener.onPasscodeEntered(s.toString());
}
}
});
addView(mEditText);
invalidate();
}
private void updateChilViewSelectionStates(int length, boolean hasFocus) {
for (int i = 0; i < mDigitCount; i++) {
getChildAt(i).setSelected(hasFocus && i == length);
}
}
/**
* Get the {#link Editable} from the EditText
*
* #return
*/
public Editable getText() {
return mEditText.getText();
}
/**
* Set text to the EditText
*
* #param text
*/
public void setText(CharSequence text) {
if (text.length() > mDigitCount) {
text = text.subSequence(0, mDigitCount);
}
mEditText.setText(text);
invalidateChildViews();
}
/**
* Clear passcode input
*/
public void clearText() {
mEditText.setText("");
invalidateChildViews();
}
private void invalidateChildViews() {
for (int i = 0; i < mDigitCount; i++) {
getChildAt(i).invalidate();
}
}
public void setPasscodeEntryListener(PasscodeEntryListener mPasscodeEntryListener) {
this.mPasscodeEntryListener = mPasscodeEntryListener;
}
#Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
requestToShowKeyboard();
return true;
}
return super.onTouchEvent(event);
}
/**
* Requests the view to be focused and the keyboard to be popped-up
*/
public void requestToShowKeyboard() {
// Make sure this view is focused
mEditText.requestFocus();
// Show keyboard
InputMethodManager inputMethodManager = (InputMethodManager) getContext()
.getSystemService(Context.INPUT_METHOD_SERVICE);
inputMethodManager.showSoftInput(mEditText, 0);
}
#Override
public OnFocusChangeListener getOnFocusChangeListener() {
return mOnFocusChangeListener;
}
#Override
public void setOnFocusChangeListener(OnFocusChangeListener l) {
mOnFocusChangeListener = l;
}
#Override
protected Parcelable onSaveInstanceState() {
Parcelable parcelable = super.onSaveInstanceState();
SavedState savedState = new SavedState(parcelable);
savedState.editTextValue = mEditText.getText().toString();
return savedState;
}
#Override
protected void onRestoreInstanceState(Parcelable state) {
SavedState savedState = (SavedState) state;
super.onRestoreInstanceState(savedState.getSuperState());
mEditText.setText(savedState.editTextValue);
mEditText.setSelection(savedState.editTextValue.length());
}
/**
* Listener that gets notified when the complete passcode has been entered
*/
public interface PasscodeEntryListener {
/**
* Called when all the digits of the passcode has been entered
*
* #param passcode - The entered passcode
*/
void onPasscodeEntered(String passcode);
}
static class SavedState extends BaseSavedState {
public static final Creator<SavedState> CREATOR =
new Creator<SavedState>() {
#Override
public SavedState createFromParcel(Parcel in) {
return new SavedState(in);
}
#Override
public SavedState[] newArray(int size) {
return new SavedState[size];
}
};
String editTextValue;
public SavedState(Parcelable superState) {
super(superState);
}
private SavedState(Parcel source) {
super(source);
editTextValue = source.readString();
}
#Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeString(editTextValue);
}
}
class DigitView extends View {
private Paint mOuterPaint, mInnerPaint;
private int mPosition = 0;
public DigitView(Context context, int position) {
this(context);
mPosition = position;
}
public DigitView(Context context) {
this(context, null);
}
public DigitView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public DigitView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
void init() {
setWillNotDraw(false);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
setLayerType(LAYER_TYPE_SOFTWARE, null);
}
mOuterPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mOuterPaint.setAlpha(255);
mOuterPaint.setDither(true);
mOuterPaint.setStyle(Paint.Style.STROKE);
mOuterPaint.setStrokeWidth(mOuterStrokeWidth);
mOuterPaint.setStrokeCap(Paint.Cap.ROUND);
mOuterPaint.setStrokeJoin(Paint.Join.ROUND);
mOuterPaint.setShadowLayer(2, 0, 0, Color.parseColor("#B4999999"));
mInnerPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mInnerPaint.setAlpha(255);
mInnerPaint.setStyle(Paint.Style.FILL_AND_STROKE);
mInnerPaint.setStrokeWidth(mInnerStrokeWidth);
mInnerPaint.setStrokeCap(Paint.Cap.ROUND);
mInnerPaint.setStrokeJoin(Paint.Join.ROUND);
mInnerPaint.setColor(mInnerColor);
invalidate();
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(mDigitWidth, mDigitWidth);
}
#Override
protected void onDraw(Canvas canvas) {
float center = getWidth() / 2;
if (isSelected()) {
mOuterPaint.setColor(mHighlightedColor);
} else {
mOuterPaint.setColor(mControlColor);
}
canvas.drawColor(Color.TRANSPARENT);
canvas.drawCircle(center, center, mDigitRadius, mOuterPaint);
if (mEditText.getText().length() > mPosition) {
canvas.drawCircle(center, center, mDigitInnerRadius, mInnerPaint);
}
}
}
Now, I want to open number keyboard as soon as dialog shows up. For this, I have a method requestToShowKeyboard() in PasscodeView.java to open keyboard.
But, keyboard doesn't open when dialog shows up. Below is the code I wrote inside onViewCreated().
#Override
public void onViewCreated(View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
mPasscodeView.requestToShowKeyboard();
}
This is how I used PasscodeView in dialog fragment layout:
<com.via.android.customview.PasscodeView
android:id="#+id/edtOptCode"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="#dimen/dimen_10_dp"
android:background="#null"
android:gravity="center"
android:padding="#dimen/dimen_8_dp"
passcodeView:digitColorBorder="#FFFFFF"
passcodeView:digitColorFilled="#9f9f9f"
passcodeView:digitRadius="12dp"
passcodeView:numDigits="6" />
If anyone can help me with this.
Solved: Solution to my problem has been turned out to this as below:
#Override
public void onViewCreated(View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
mPasscodeView.post(new Runnable() {
#Override
public void run() {
mPasscodeView.requestToShowKeyboard();
}
});
}
Instead of calling in onViewCreated(), try calling like
mPasscodeView.post(new Runnable() {
#Override
public void run() {
mPasscodeView.requestToShowKeyboard();
}
});
So this will add the runnable to the queue to execute in next pass.
How can i set the default MenuItem for the official BottomNavigationView (com.android.support:design:25.0.1)?
If I call programmatically menuItem.setCheckable(true).setChecked(true) the zoom effect is not performed and the BottomNavigationView shows like this:
There is more simpler way to do this since Android Support Library 25.3.0 :
bottomNavigationView.setSelectedItemId(R.id.id_of_item_you_want_to_select_as_default);
I achieved this in a much simpler way:
//R.id.bottom_bar_icon is the icon you would like clicked by default
bottomNavigationView.getMenu().performIdentifierAction(R.id.bottom_bar_icon, 0);
//set the corresponding menu item to checked = true
//and the other items to checked = false
bottomNavigationView.getMenu().getItem(0).setChecked(false);
bottomNavigationView.getMenu().getItem(1).setChecked(true);
bottomNavigationView.getMenu().getItem(2).setChecked(false);
In the end I was able to achieve this issue extending the BottomNavigationView like this:
public class RichBottomNavigationView extends BottomNavigationView {
private ViewGroup mBottomItemsHolder;
private int mLastSelection = INVALID_POSITION;
private Drawable mShadowDrawable;
private boolean mShadowVisible = true;
private int mWidth;
private int mHeight;
private int mShadowElevation = 2;
public RichBottomNavigationView(Context context) {
super(context);
init();
}
public RichBottomNavigationView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public RichBottomNavigationView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
;
}
private void init() {
mShadowDrawable = ContextCompat.getDrawable(getContext(), R.drawable.shadow);
if (mShadowDrawable != null) {
mShadowDrawable.setCallback(this);
}
setBackgroundColor(ContextCompat.getColor(getContext(), android.R.color.transparent));
setShadowVisible(true);
setWillNotDraw(false);
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h + mShadowElevation, oldw, oldh);
mWidth = w;
mHeight = h;
updateShadowBounds();
}
private void updateShadowBounds() {
if (mShadowDrawable != null && mBottomItemsHolder != null) {
mShadowDrawable.setBounds(0, 0, mWidth, mShadowElevation);
}
ViewCompat.postInvalidateOnAnimation(this);
}
#Override
public void draw(Canvas canvas) {
super.draw(canvas);
if (mShadowDrawable != null && mShadowVisible) {
mShadowDrawable.draw(canvas);
}
}
public void setShadowVisible(boolean shadowVisible) {
setWillNotDraw(!mShadowVisible);
updateShadowBounds();
}
public int getShadowElevation() {
return mShadowVisible ? mShadowElevation : 0;
}
public int getSelectedItem() {
return mLastSelection = findSelectedItem();
}
#CallSuper
public void setSelectedItem(int position) {
if (position >= getMenu().size() || position < 0) return;
View menuItemView = getMenuItemView(position);
if (menuItemView == null) return;
MenuItemImpl itemData = ((MenuView.ItemView) menuItemView).getItemData();
itemData.setChecked(true);
boolean previousHapticFeedbackEnabled = menuItemView.isHapticFeedbackEnabled();
menuItemView.setSoundEffectsEnabled(false);
menuItemView.setHapticFeedbackEnabled(false); //avoid hearing click sounds, disable haptic and restore settings later of that view
menuItemView.performClick();
menuItemView.setHapticFeedbackEnabled(previousHapticFeedbackEnabled);
menuItemView.setSoundEffectsEnabled(true);
mLastSelection = position;
}
#Override
protected Parcelable onSaveInstanceState() {
Parcelable superState = super.onSaveInstanceState();
BottomNavigationState state = new BottomNavigationState(superState);
mLastSelection = getSelectedItem();
state.lastSelection = mLastSelection;
return state;
}
#Override
protected void onRestoreInstanceState(Parcelable state) {
if (!(state instanceof BottomNavigationState)) {
super.onRestoreInstanceState(state);
return;
}
BottomNavigationState bottomNavigationState = (BottomNavigationState) state;
mLastSelection = bottomNavigationState.lastSelection;
dispatchRestoredState();
super.onRestoreInstanceState(bottomNavigationState.getSuperState());
}
private void dispatchRestoredState() {
if (mLastSelection != 0) { //Since the first item is always selected by the default implementation, dont waste time
setSelectedItem(mLastSelection);
}
}
private View getMenuItemView(int position) {
View bottomItem = mBottomItemsHolder.getChildAt(position);
if (bottomItem instanceof MenuView.ItemView) {
return bottomItem;
}
return null;
}
private int findSelectedItem() {
int itemCount = getMenu().size();
for (int i = 0; i < itemCount; i++) {
View bottomItem = mBottomItemsHolder.getChildAt(i);
if (bottomItem instanceof MenuView.ItemView) {
MenuItemImpl itemData = ((MenuView.ItemView) bottomItem).getItemData();
if (itemData.isChecked()) return i;
}
}
return INVALID_POSITION;
}
#Override
protected void onFinishInflate() {
super.onFinishInflate();
mBottomItemsHolder = (ViewGroup) getChildAt(0);
updateShadowBounds();
//This sucks.
MarginLayoutParams layoutParams = (MarginLayoutParams) mBottomItemsHolder.getLayoutParams();
layoutParams.topMargin = (mShadowElevation + 2) / 2;
}
static class BottomNavigationState extends BaseSavedState {
public int lastSelection;
#RequiresApi(api = Build.VERSION_CODES.N)
public BottomNavigationState(Parcel in, ClassLoader loader) {
super(in, loader);
lastSelection = in.readInt();
}
public BottomNavigationState(Parcelable superState) {
super(superState);
}
#Override
public void writeToParcel(#NonNull Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeInt(lastSelection);
}
public static final Parcelable.Creator<NavigationView.SavedState> CREATOR
= ParcelableCompat.newCreator(new ParcelableCompatCreatorCallbacks<NavigationView.SavedState>() {
#Override
public NavigationView.SavedState createFromParcel(Parcel parcel, ClassLoader loader) {
return new NavigationView.SavedState(parcel, loader);
}
#Override
public NavigationView.SavedState[] newArray(int size) {
return new NavigationView.SavedState[size];
}
});
}
}
and calling setSelectedItem(2)
I just followed this tutorial, to create a custom View as an item of a GridLayout.
That's my CustomView
public class RowView extends View{
boolean touchOn;
boolean mDownTouch = false;
private OnToggledListener toggledListener;
int _IdRow = 0;
int _IdColumn = 0;
public RowView(Context context, int Rows, int Columns) {
super(context);
this._IdRow = Rows;
this._IdColumn = Columns;
init();
}
public RowView(Context context) {
super(context);
init();
}
public RowView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public RowView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
touchOn = false;
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec),
MeasureSpec.getSize(heightMeasureSpec));
}
#Override
protected void onDraw(Canvas canvas) {
if (touchOn) {
canvas.drawColor(Color.RED);
} else {
canvas.drawColor(Color.GRAY);
}
}
//onClick not possible to use on custom View so, onTouchEvent is the solution
#Override
public boolean onTouchEvent(MotionEvent event) {
super.onTouchEvent(event);
switch (event.getAction()) {
//if Click
case MotionEvent.ACTION_DOWN:
touchOn = !touchOn;
invalidate();
if(toggledListener != null){
toggledListener.OnToggled(this, touchOn);
}
mDownTouch = true;
return true;
case MotionEvent.ACTION_UP:
if (mDownTouch) {
mDownTouch = false;
performClick();
return true;
}
}
return false;
}
#Override
public boolean performClick() {
super.performClick();
return true;
}
public void setOnToggledListener(OnToggledListener listener){
toggledListener = listener;
}
public int get_IdRow() {
return _IdRow;
}
public int get_IdColumn() {
return _IdColumn;
}
On this class I can detect when user clicks on an item of GridLayout and change it to another color, that's ok.
But the problem comes at the time to create this :
This is my MainActivity where I show the GridLayout :
int numOfCol = mGridLayout.getColumnCount();
int numOfRow = mGridLayout.getRowCount();
mRowViews = new RowView[numOfCol*numOfRow];
for(int yPos=0; yPos<numOfRow; yPos++){
for(int xPos=0; xPos<numOfCol; xPos++){
RowView tView = new RowView(this, xPos, yPos);
tView.setOnToggledListener(this);
mRowViews[yPos*numOfCol + xPos] = tView;
mGridLayout.addView(tView);
}
}
mGridLayout.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener(){
#Override
public void onGlobalLayout() {
final int MARGIN = 5;
int pWidth = mGridLayout.getWidth();
int pHeight = mGridLayout.getHeight();
int numOfCol = mGridLayout.getColumnCount();
int numOfRow = mGridLayout.getRowCount();
int w = pWidth/numOfCol;
int h = pHeight/numOfRow;
for(int yPos=0; yPos<numOfRow; yPos++){
for(int xPos=0; xPos<numOfCol; xPos++){
GridLayout.LayoutParams params =
(GridLayout.LayoutParams)mRowViews[yPos*numOfCol + xPos].getLayoutParams();
params.width = w - 2*MARGIN;
params.height = h - 2*MARGIN;
params.setMargins(MARGIN, MARGIN, MARGIN, MARGIN);
mRowViews[yPos*numOfCol + xPos].setLayoutParams(params);
}
}
}});
Also there is a method of the Interface OnToggledListener that gives to me the row and column of my GridLayout when an item of it is clicked :
#Override
public void OnToggled(MyView v, boolean touchOn) {
//get the id string
String idString = v.get_IdRow() + ":" + v.get_IdColumn();
}
I'd like to avoid to create that mGridLayout.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() because it fills on the screen thing that I don't want... I tried to put GridLayout 6x6 with android:layout_height="400dp" and it only show 3x3 and this is the LogCat message
D/android.widget.GridLayout: vertical constraints: y6-y0>=1749, y6-y5<=291, y5-y4<=291, y4-y3<=291, y3-y2<=291, y2-y1<=291, y1-y0<=291 are inconsistent; permanently removing: y6-y5<=291.
I'd like to do something like GridLayout[row][colum] to get the color of background and then do stuff, but I'm not able to find this solution.
For simplifying, you can implement a custom Board view wrapping the GridLayout and related logic. Below I report a possible approach.
Expectation here is to have an ItemView for representing one single cell in the board.
public class Board extends FrameLayout implements View.OnClickListener {
private GridLayout mGridView;
private int mRowsCount;
private int mColsCount;
private int mCellSpace;
private OnItemClickListener mOnItemClickListener;
public Board(Context context) {
super(context);
init(context, null);
}
// other constructors
private void init(Context context, AttributeSet attrs) {
// default values
mRowsCount = 1;
mColsCount = 1;
View layout = inflate(getContext(), R.layout.view_lights_board, null);
mGridView = (GridLayout) layout.findViewById(R.id.view_grid);
mGridView.setRowCount(mRowsCount);
mGridView.setColumnCount(mColsCount);
mGridView.post(new Runnable() {
#Override
public void run() {
int width = getMeasuredWidth() / getColumnsCount();
int height = getMeasuredHeight() / getRowsCount();
for (int i = 0; i < getRowsCount(); i++) {
for (int j = 0; j < getColumnsCount(); j++) {
GridLayout.LayoutParams params = (GridLayout.LayoutParams)
getChildAt(i, j).getLayoutParams();
params.width = width;
params.height = height;
getChildAt(i, j).setLayoutParams(params);
}
}
}
});
addView(layout);
}
// this method allows to dinamically create grid
public void buildChildren(int rowsCount, int colsCount) {
mRowsCount = rowsCount;
mColsCount = colsCount;
mGridView.setRowCount(mRowsCount);
mGridView.setColumnCount(mColsCount);
buildChildren();
}
public void buildChildren() {
for (int i = 0; i < getRowsCount(); i++) {
for (int j = 0; j < getColumnsCount(); j++) {
ItemView view = new ItemView(getContext(), i, j);
view.setOnClickListener(this);
mGridView.addView(view);
}
}
}
public void setOnItemClickListener(OnItemClickListener listener) {
mOnItemClickListener = listener;
}
public ItemView getChildAt(int rowIndex, int columnIndex) {
int index = (getColumnsCount() * rowIndex) + columnIndex;
return (ItemView) mGridView.getChildAt(index);
}
public boolean isTouchOn(int rowIndex, int columnIndex) {
return getChildAt(rowIndex, columnIndex).isTouchOn();
}
public int getColumnsCount() {
return mGridView.getColumnCount();
}
public int getRowsCount() {
return mGridView.getRowCount();
}
#Override
public void onClick(View v) {
if (v instanceof ItemView) {
ItemView view = (ItemView) v;
if (mOnItemClickListener != null) {
mOnItemClickListener.onItemClick(view);
}
}
}
public interface OnItemClickListener {
void onItemClick(ItemView view);
}
}
In your Activity layout you will have something like this (here I assume your app package is com.android.example):
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<com.android.example.Board
android:id="#+id/grid"
android:layout_width="match_parent"
android:layout_height="400dp" />
</FrameLayout>
And this is possible implementation of the Activity:
public class MainActivity extends AppCompatActivity implements LightsOutBoard.OnItemClickListener {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Board board = (Board) findViewById(R.id.grid);
board.setOnItemClickListener(this);
board.buildChildren(3, 3);
}
#Override
public void onItemClick(ItemView view) {
String text = view.getRowIndex() + " - " + view.getColumnIndex();
Toast.makeText(this, text, Toast.LENGTH_SHORT).show();
}
}
Hope this could help.