Make RelativeLayout checkable - android

I try to write my own CheckBox using RelativeLayout with TextView and FrameLayout (with selector on background) inside.
I have setDuplicateParentStateEnabled(true) for FrameLayout that it take checkable status from parent, but why make RelativeLayout checkable I don't know.
public class MyCheckbox extends RelativeLayout implements OnClickListener, Checkable
{
private static final int ID_CHECKBOX = -1234411;
private static final int ID_TEXTVIEW = -1234412;
private static final int ID_PARENT = -1234413;
private TextView textView;
private FrameLayout checkBox;
private boolean isChecked;
public MyCheckbox(Context context)
{
this(context, null);
}
public MyCheckbox(Context context, AttributeSet attrs)
{
super(context, attrs);
LayoutParams lp;
setId(ID_PARENT);
setOnClickListener(this);
// checkBox
checkBox = new FrameLayout(context);
checkBox.setId(ID_CHECKBOX);
checkBox.setDuplicateParentStateEnabled(true);
// textView
textView = new TextView(context);
textView.setId(ID_TEXTVIEW);
textView.setOnClickListener(this);
boolean checkboxToRight = false;
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.MaptrixCheckbox);
for (int i = 0; i < a.getIndexCount(); i++)
{
int attr = a.getIndex(i);
switch (attr)
{
case R.styleable.MyCheckbox_checkboxToRight:
checkboxToRight = a.getBoolean(attr, false);
break;
case R.styleable.MyCheckbox_checkMark:
checkBox.setBackgroundDrawable(a.getDrawable(attr));
break;
case R.styleable.MyCheckbox_text:
textView.setText(a.getText(attr));
break;
}
}
a.recycle();
if (checkboxToRight)
{
lp = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
lp.addRule(ALIGN_PARENT_RIGHT);
lp.addRule(CENTER_VERTICAL);
lp.setMargins(margins, 0, margins, 0);
addView(checkBox, lp);
lp = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
lp.addRule(CENTER_VERTICAL);
lp.addRule(LEFT_OF, ID_CHECKBOX);
addView(textView, lp);
}
else
{
lp = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
lp.addRule(CENTER_VERTICAL);
lp.setMargins(margins, 0, margins, 0);
addView(checkBox, lp);
lp = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
lp.addRule(CENTER_VERTICAL);
lp.addRule(RIGHT_OF, ID_CHECKBOX);
addView(textView, lp);
}
refreshDrawableState();
}
#Override
public void onClick(View v)
{
int id = v.getId();
if (id == ID_PARENT || id == ID_TEXTVIEW) toggle();
}
public boolean isChecked()
{
return isChecked;
}
public void setChecked(boolean chacked)
{
isChecked = chacked;
refreshDrawableState();
}
#Override
public void toggle()
{
isChecked = !isChecked;
refreshDrawableState();
}
}

There seem to be two UI patterns for multi-select lists in Holo (in the context of ActionMode): using checkboxes or highlighting.
If all you need is highlighting, you can use this simpler method:
Make your list item layout use for the root a class like this one:
public class CheckableRelativeLayout extends RelativeLayout implements Checkable {
private static final int[] STATE_CHECKABLE = {R.attr.state_pressed};
boolean checked = false;
public void setChecked(boolean checked) {
this.checked = checked;
refreshDrawableState();
}
public boolean isChecked() {
return checked;
}
public void toggle() {
setChecked(!checked);
}
#Override
protected int[] onCreateDrawableState(int extraSpace) {
int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
if (checked) mergeDrawableStates(drawableState, STATE_CHECKABLE);
return drawableState;
}
}
and make sure the root element in your list item layout uses
android:background="?android:attr/listChoiceBackgroundIndicator"
I know this is not really an answer to your question (you wanted an actual checkbox), but I haven't seen this documented anywhere.
The ListView uses the Checkable interface to determine whether it can rely on the list item element to display the checkable state or if it should try to display it itself.

I just need override method:
private static final int[] STATE_CHECKABLE = {android.R.attr.state_checked};
#Override
protected int[] onCreateDrawableState(int extraSpace)
{
int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
if (isChecked) mergeDrawableStates(drawableState, STATE_CHECKABLE);
return drawableState;
}
Now all works.

Nik's answer is partially right for me, you need added android.R.attr.state_checkable into the STATE_CHECKABLE array, and set the corresponding lenth (2), or else it fails to work.
private static final int[] CHECKED_STATE_SET = {android.R.attr.state_checked, android.R.attr.state_checkable};
#Override
protected int[] onCreateDrawableState(int extraSpace)
{
int[] result = super.onCreateDrawableState(extraSpace + CHECKED_STATE_SET.length);
if(mChecked) mergeDrawableStates(result, CHECKED_STATE_SET);
return result;
}

http://developer.android.com/reference/android/view/View.html#setClickable%28boolean%29
public MyCheckbox(Context context)
{
this(context, null);
this.setClickable(true);
}
public MyCheckbox(Context context, AttributeSet attrs)
{
super(context, attrs);
this.setClickable(true);
//......
}

Related

Click listener is not working on surface view inside frame layout

I have read out many questions but I don't get the answer to mine.
So I am posting this new question.
I am working on a live streaming app. Basically I want to change the surface view size on click event like Whatsapp video calling.
I have two scenarios:-
Scenario 1:- One is relative layout which contains a surface view and there is a frame layout which also contains a surface view and I want to exchange when click event happens but the Click event listener is not working.
XML file
<FrameLayout
android:id="#+id/liveStreaming_videoAndControlsContainer"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/black">
<com.sos.GD_Live.utils.liveUtils.VideoGridContainer
android:id="#+id/liveStreaming_videoContainer"
android:layout_width="match_parent"
android:layout_height="match_parent">
</com.sos.GD_Live.utils.liveUtils.VideoGridContainer>
<FrameLayout
android:id="#+id/videoChat_localVideoRender"
android:layout_marginBottom="130dp"
android:layout_marginRight="20dp"
android:layout_width="100dp"
android:onClick="frameclick"
android:visibility="visible"
android:layout_height="150dp"
android:layout_gravity="bottom|right"
/>
Its parent layout is also a frame layout
public class VideoGridContainer extends RelativeLayout implements Runnable {
private static final int MAX_USER = 4;
private static final int STAT_LEFT_MARGIN = 34;
private static final int STAT_TEXT_SIZE = 10;
private SparseArray<ViewGroup> mUserViewList = new SparseArray<>(MAX_USER);
private List<Integer> mUidList = new ArrayList<>(MAX_USER);
private Handler mHandler;
private int mStatMarginBottom;
private VideoViewEventListener mEventListener;
public VideoGridContainer(Context context) {
super(context);
init();
}
public VideoGridContainer(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public VideoGridContainer(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
mHandler = new Handler(getContext().getMainLooper());
}
public void addUserVideoSurface(int uid, SurfaceView surface, boolean isLocal) {
if (surface == null) {
return;
}
int id = -1;
if (isLocal) {
if (mUidList.contains(0)) {
mUidList.remove((Integer) 0);
mUserViewList.remove(0);
}
if (mUidList.size() == MAX_USER) {
mUidList.remove(0);
mUserViewList.remove(0);
}
id = 0;
} else {
if (mUidList.contains(uid)) {
mUidList.remove((Integer) uid);
mUserViewList.remove(uid);
}
if (mUidList.size() < MAX_USER) {
id = uid;
}
}
if (id == 0) mUidList.add(0, uid);
else mUidList.add(uid);
if (id != -1) {
mUserViewList.append(uid, createVideoView(surface));
requestGridLayout();
}
}
private ViewGroup createVideoView(SurfaceView surface) {
RelativeLayout layout = new RelativeLayout(getContext());
layout.setId(surface.hashCode());
LayoutParams videoLayoutParams =
new LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT);
layout.addView(surface, videoLayoutParams);
TextView text = new TextView(getContext());
text.setId(layout.hashCode());
LayoutParams textParams =
new LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
textParams.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM, RelativeLayout.TRUE);
textParams.bottomMargin = mStatMarginBottom;
textParams.leftMargin = STAT_LEFT_MARGIN;
text.setTextColor(Color.WHITE);
text.setTextSize(STAT_TEXT_SIZE);
layout.addView(text, textParams);
return layout;
}
public void removeUserVideo(int uid, boolean isLocal) {
if (isLocal && mUidList.contains(0)) {
mUidList.remove((Integer) 0);
mUserViewList.remove(0);
} else if (mUidList.contains(uid)) {
mUidList.remove((Integer) uid);
mUserViewList.remove(uid);
}
requestGridLayout();
if (getChildCount() == 0) {
mHandler.removeCallbacks(this);
}
}
private void requestGridLayout() {
removeAllViews();
layout(mUidList.size());
}
private void layout(int size) {
LayoutParams[] params = getParams(size);
for (int i = 0; i < size; i++) {
addView(mUserViewList.get(mUidList.get(i)), params[i]);
}
}
private LayoutParams[] getParams(int size) {
int width = getMeasuredWidth();
int height = getMeasuredHeight();
LayoutParams[] array =
new LayoutParams[size];
for (int i = 0; i < size; i++) {
if (i == 0) {
array[0] = new LayoutParams(
LayoutParams.MATCH_PARENT,
LayoutParams.MATCH_PARENT);
array[0].addRule(RelativeLayout.ALIGN_PARENT_TOP, RelativeLayout.TRUE);
array[0].addRule(RelativeLayout.ALIGN_PARENT_LEFT, RelativeLayout.TRUE);
} else if (i == 1) {
array[1] = new LayoutParams(width, height / 2);
array[0].height = array[1].height;
array[1].addRule(RelativeLayout.BELOW, mUserViewList.get(mUidList.get(0)).getId());
array[1].addRule(RelativeLayout.ALIGN_PARENT_LEFT, RelativeLayout.TRUE);
} else if (i == 2) {
array[i] = new LayoutParams(width / 2, height / 2);
array[i - 1].width = array[i].width;
array[i].addRule(RelativeLayout.RIGHT_OF, mUserViewList.get(mUidList.get(i - 1)).getId());
array[i].addRule(RelativeLayout.ALIGN_TOP, mUserViewList.get(mUidList.get(i - 1)).getId());
} else if (i == 3) {
array[i] = new LayoutParams(width / 2, height / 2);
array[0].width = width / 2;
array[1].addRule(RelativeLayout.BELOW, 0);
array[1].addRule(RelativeLayout.ALIGN_PARENT_LEFT, 0);
array[1].addRule(RelativeLayout.RIGHT_OF, mUserViewList.get(mUidList.get(0)).getId());
array[1].addRule(RelativeLayout.ALIGN_PARENT_TOP, RelativeLayout.TRUE);
array[2].addRule(RelativeLayout.ALIGN_PARENT_LEFT, RelativeLayout.TRUE);
array[2].addRule(RelativeLayout.RIGHT_OF, 0);
array[2].addRule(RelativeLayout.ALIGN_TOP, 0);
array[2].addRule(RelativeLayout.BELOW, mUserViewList.get(mUidList.get(0)).getId());
array[3].addRule(RelativeLayout.BELOW, mUserViewList.get(mUidList.get(1)).getId());
array[3].addRule(RelativeLayout.RIGHT_OF, mUserViewList.get(mUidList.get(2)).getId());
}
}
return array;
}
#Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
clearAllVideo();
}
private void clearAllVideo() {
removeAllViews();
mUserViewList.clear();
mUidList.clear();
mHandler.removeCallbacks(this);
}
#Override
public void run() {
}
public void setItemEventHandler(VideoViewEventListener listener) {
this.mEventListener = listener;
}
}
This code where in Java file, when I added surface view for both of the layouts
mVideoLayout = findViewById(R.id.liveStreaming_videoContainer);
mVideoLayout.addUserVideoSurface(0, surface, true);
SurfaceView surface = prepareRtcVideo(remotehost, false);
surface.setZOrderMediaOverlay(true);
mCameraBtn.setVisibility(View.VISIBLE);
remotehostlist.add(remotehost);
surface.setZOrderOnTop(true);
runOnUiThread(() -> {
surface.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View view, MotionEvent motionEvent) {
Toast.makeText(LiveStreamingActivity.this, "it's good", Toast.LENGTH_SHORT).show();
return true;
}
});
surface.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Toast.makeText(LiveStreamingActivity.this, "it's good", Toast.LENGTH_SHORT).show();
}
});
});
mLayoutSmallView.addView(surface);
mLayoutSmallView.setVisibility(View.VISIBLE);
mLayoutSmallView.setOnFocusChangeListener(new View.OnFocusChangeListener() {
#Override
public void onFocusChange(View view, boolean b) {
Toast.makeText(LiveStreamingActivity.this, "Focus changed", Toast.LENGTH_SHORT).show();
}
});
mRtcEngine.setupLocalVideo(new VideoCanvas(surface, VideoCanvas.RENDER_MODE_HIDDEN, 0));
mRtcEngine.enableVideo();
I have already tried onclick in XML, ontouchlistener, onfocuschange listener. But no one of them is working for it. Also tried is focusable.
The problem is I am unable to click the frame layout named mLayoutSmallView.
I want to switch the surface views when it is clicked.

how to get expandable textview width to set its trim length to 1 line

i'm using expandable textview to display some part of the text and when user clicks on this textview then user can see whole String of that text for that i'm using this example but the problem is the trim length of the expandable textview its set to fixed, but i want to set the trim length dynamic based on screen size with only one line, when i use trim_length = 200 the text displayed is of 3 lines, here is my code...
ExpandableTextView.java
public class ExpandableTextView extends TextView {
private static final int DEFAULT_TRIM_LENGTH = 200;
private static final String ELLIPSIS = ".....";
private CharSequence originalText;
private CharSequence trimmedText;
private BufferType bufferType;
private boolean trim = true;
private int trimLength;
public ExpandableTextView(Context context) {
this(context, null);
}
public ExpandableTextView(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.ExpandableTextView);
this.trimLength = typedArray.getInt(R.styleable.ExpandableTextView_trimLength, DEFAULT_TRIM_LENGTH);
typedArray.recycle();
setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
trim = !trim;
setText();
requestFocusFromTouch();
}
});
}
private void setText() {
super.setText(getDisplayableText(), bufferType);
}
private CharSequence getDisplayableText() {
return trim ? trimmedText : originalText;
}
#Override
public void setText(CharSequence text, BufferType type) {
originalText = text;
trimmedText = getTrimmedText(text);
bufferType = type;
setText();
}
private CharSequence getTrimmedText(CharSequence text) {
if (originalText != null && originalText.length() > trimLength) {
return new SpannableStringBuilder(originalText, 0, trimLength + 1).append(ELLIPSIS);
} else {
return originalText;
}
}
public CharSequence getOriginalText() {
return originalText;
}
public void setTrimLength(int trimLength) {
this.trimLength = trimLength;
trimmedText = getTrimmedText(originalText);
setText();
}
public int getTrimLength() {
return trimLength;
}
}
ExpandableTextView expandableTextView = (ExpandableTextView) findViewById(R.id.lorem_ipsum);
expandableTextView.setText(yourText);
you can do it like this
public class ExpandableTextView extends TextView
{
// copy off TextView.LINES
private static final int MAXMODE_LINES = 1;
// private OnExpandListener onExpandListener;
private final int maxLines;
private boolean expanded;
public ExpandableTextView(final Context context)
{
this(context, null);
}
public ExpandableTextView(final Context context, final AttributeSet attrs)
{
this(context, attrs, 0);
}
public ExpandableTextView(final Context context, final AttributeSet attrs, final int defStyle)
{
super(context, attrs, defStyle);
// read attributes
final TypedArray attributes = context.obtainStyledAttributes(attrs, R.styleable.ExpandableTextView, defStyle, 0);
// this.animationDuration = attributes.getInt(R.styleable.ExpandableTextView_trimLength, 200);
attributes.recycle();
// keep the original value of maxLines
this.maxLines = this.getMaxLines();
}
#Override
public int getMaxLines()
{
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN)
{
return super.getMaxLines();
}
try
{
final Field mMaxMode = TextView.class.getField("mMaxMode");
mMaxMode.setAccessible(true);
final Field mMaximum = TextView.class.getField("mMaximum");
mMaximum.setAccessible(true);
final int mMaxModeValue = (int) mMaxMode.get(this);
final int mMaximumValue = (int) mMaximum.get(this);
return mMaxModeValue == MAXMODE_LINES ? mMaximumValue : -1;
}
catch (final Exception e)
{
return -1;
}
}
public boolean toggle()
{
return this.expanded
? this.collapse()
: this.expand();
}
public boolean expand()
{
if (!this.expanded && this.maxLines >= 0)
{
// set maxLines to MAX Integer, so we can calculate the expanded height
this.setMaxLines(Integer.MAX_VALUE);
// keep track of current status
ExpandableTextView.this.expanded = true;
return true;
}
return false;
}
public boolean collapse()
{
if (this.expanded && this.maxLines >= 0)
{
ExpandableTextView.this.setMaxLines(ExpandableTextView.this.maxLines);
// keep track of current status
ExpandableTextView.this.expanded = false;
return true;
}
return false;
}
}
activity.xml
<com.yourpackagename.ExpandableTextView
android:id="#+id/expandableTextView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="1"
/>
activity.java
ExpandableTextView expandableTextView = (ExpandableTextView) this.findViewById(R.id.expandableTextView);
expandableTextView.setOnClickListener(new View.OnClickListener()
{
#SuppressWarnings("ConstantConditions")
#Override
public void onClick(final View v)
{
expandableTextView.toggle();
}
});

GridLayout with view dynamic get row/column

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.

RadioGroup onCheckedChanged getting an index higher than ChildCount

I'm creating dinamically a LinearLayout (a custom Class that extends LinearLayout) with a RadioGroup inside it. I'm adding RadioButtons dinamically also. This LinearLayout class is used in several Activities and Fragments.
The strange behaviour is that the second time I add LinearLayout, when OnCheckedChanged is called, a get an index that doesn't exists in my current RadioGroup. I't seems that every time I create a new Instance of my LinearLayout, RadioGroup thinks that RadioButtons added previously are still there.
For example, if I create a custom LinearLayout and I add four items and after that I create another LinearLayout (in other Activity) with other 4 items, when I click in the first item (on the second Activity), I get a 5 as clicked item position.
This is my custom LinearLayout class:
public class AgrupacionConmutadorLayout extends LinearLayout{
private static final String TAG = "AgrupacionConmutadorLayout";
private int[] mColorFromLevel = {R.color.inspeccion_aparato_agrupacion_color_level_1, R.color.inspeccion_aparato_agrupacion_color_level_2,
R.color.inspeccion_aparato_agrupacion_color_level_3, R.color.inspeccion_aparato_agrupacion_color_level_4, R.color.inspeccion_aparato_agrupacion_color_level_5};
private Context mContext;
private LinearLayout dataContainer;
private TextView mTitle;
private RelativeLayout mSubAgrupacionesContainer;
private LinearLayout mDataAndSubAgrupacionesContainer;
private RadioGroup mConmutadorContainer;
private ArrayList<View> mLayoutsDataContained = new ArrayList<View>();
private int mLevel;
private LinearLayout mTitleContainer;
private InspeccionesFormWidgetReceiver mModificationReceiver;
private boolean isEdicion;
public AgrupacionConmutadorLayout(Context context) {
super(context);
this.mContext = context;
init(null, 0);
}
public AgrupacionConmutadorLayout(Context context, AttributeSet attrs) {
super(context, attrs);
this.mContext = context;
init(attrs, 0);
}
public AgrupacionConmutadorLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
this.mContext = context;
init(attrs, defStyle);
}
private void init(AttributeSet attrs, int defStyle) {
dataContainer = (LinearLayout) LayoutInflater.from(mContext).inflate(R.layout.ins_agrupacion_conmutador_container, this);
mTitle = (TextView) dataContainer.findViewById(R.id.ins_agrupacion_container_title);
mSubAgrupacionesContainer = (RelativeLayout) findViewById(R.id.rl_ins_subagrupacion_container);
mConmutadorContainer = (RadioGroup) findViewById(R.id.ins_agrupacion_conmutador_radio_group);
mDataAndSubAgrupacionesContainer = (LinearLayout) findViewById(R.id.ll_ins_data_and_subagrupacion_container);
mTitleContainer = (LinearLayout) findViewById(R.id.ll_ins_agrupacion_container_title);
mConmutadorContainer.removeAllViews();
mConmutadorContainer.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
#Override
public void onCheckedChanged(RadioGroup radioGroup, int pos) {
Log.d(TAG, "onCheckedChanged Clicked pos: " + pos);
Log.d(TAG, "onCheckedChanged RadioGroup size: " + radioGroup.getChildCount());
Log.d(TAG, "onCheckedChanged " + mLayoutsDataContained.size());
//OnCheckedChanged comienza a devolver en 1, al ArrayList en 0
for (int i = 1; i < mLayoutsDataContained.size() + 1; i++) {
if (i == pos) {
mLayoutsDataContained.get(i - 1).setVisibility(View.VISIBLE);
} else {
mLayoutsDataContained.get(i - 1).setVisibility(View.GONE);
}
}
}
});
}
public void initialice(AgrupacionSios agrupacionSios, InspeccionesFormWidgetReceiver modificationReceiver, boolean isEdicion) {
this.mModificationReceiver = modificationReceiver;
this.isEdicion = isEdicion;
mTitle.setText(agrupacionSios.getTitle());
mTitle.setTextColor(mContext.getResources().getColor(mColorFromLevel[agrupacionSios.getLevel()]));
if (agrupacionSios.isOcultarContenedor()) mTitleContainer.setVisibility(View.GONE);
if (agrupacionSios.getNumOfSons() > 0) {
drawSubAgrupaciones(agrupacionSios);
}
this.refreshDrawableState();
}
private void drawSubAgrupaciones(AgrupacionSios agrupacionSios) {
ViewGroup.LayoutParams layoutParams = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
ArrayList<AgrupacionSios> agrupaciones = agrupacionSios.getAgrupacionesHijos();
Iterator itAgr = agrupaciones.iterator();
while (itAgr.hasNext()) {
AgrupacionSios agrupacionHija = (AgrupacionSios) itAgr.next();
if (agrupacionHija.getNumOfLevels() > 0 || agrupacionHija.getNumOfSons() > 0) {
switch (agrupacionHija.getTipoSubAgrupaciones()) {
case CONMUTADOR:
AgrupacionConmutadorLayout v = new AgrupacionConmutadorLayout(mContext);
v.initialice(agrupacionHija, mModificationReceiver, isEdicion);
mSubAgrupacionesContainer.addView(v, layoutParams);
mLayoutsDataContained.add(v);
break;
case COLUMNAS:
AgrupacionColumnasLayout vCol = new AgrupacionColumnasLayout(mContext);
vCol.initialice(agrupacionHija, mModificationReceiver, isEdicion);
mSubAgrupacionesContainer.addView(vCol, layoutParams);
mLayoutsDataContained.add(vCol);
break;
default:
AgrupacionGenericaLayout vGen = new AgrupacionGenericaLayout(mContext);
vGen.isSonOfConmutador(true);
vGen.initialice(agrupacionHija, mModificationReceiver, isEdicion);
mSubAgrupacionesContainer.addView(vGen, layoutParams);
mLayoutsDataContained.add(vGen);
break;
}
}
//Añadimos el botón de conmutación correspondiente a la subagrupación.
addAgrupacionToConmutadorContainer(agrupacionHija);
}
mConmutadorContainer.check(1);
}
public void setBackgroundColor(int color) {
setBackgroundResource(color);
}
private void addAgrupacionToConmutadorContainer(AgrupacionSios mAgrupacion) {
ViewGroup.LayoutParams layoutParams = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);
RadioButton b = new RadioButton(mContext);
b.setLayoutParams(layoutParams);
b.setText(mAgrupacion.getTitle());
b.setButtonDrawable(android.R.color.transparent);
b.setBackground(mContext.getResources().getDrawable(R.drawable.ins_conmutador_radio_background));
b.setPadding(20,5,20,5);
RadioGroup.LayoutParams params
= new RadioGroup.LayoutParams(mContext, null);
params.setMargins(10, 0, 10, 0);
b.setLayoutParams(params);
mConmutadorContainer.addView(b);
}
}
And this is the way I create AgrupacionConmutadorLayout on Activities & Fragments:
AgrupacionConmutadorLayout vConm = new AgrupacionConmutadorLayout(this);
vConm.initialice(agrupacion, this, isEdicion);
vConm.setVisibility(View.VISIBLE);
mAgrupacionesContainer.addView(vConm, layoutParams);
-----------------SOLUTION---------------------------
Marius answer was the key, I was getting clicked RadioButton id, not his position inside RadioGroup.
Finally I have done it by using tags. I set its order to every RadioButton as a tag:
private void addAgrupacionToConmutadorContainer(AgrupacionSios mAgrupacion) {
ViewGroup.LayoutParams layoutParams = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);
RadioButton b = new RadioButton(mContext);
b.setLayoutParams(layoutParams);
b.setText(mAgrupacion.getTitle());
b.setButtonDrawable(android.R.color.transparent);
b.setBackground(mContext.getResources().getDrawable(R.drawable.ins_conmutador_radio_background));
b.setPadding(20, 5, 20, 5);
RadioGroup.LayoutParams params
= new RadioGroup.LayoutParams(mContext, null);
params.setMargins(10, 0, 10, 0);
b.setLayoutParams(params);
b.setTag(mConmutadorContainer.getChildCount());
mConmutadorContainer.addView(b);
}
And I get that tag in OnCheckedChangeListener:
mConmutadorContainer.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
#Override
public void onCheckedChanged(RadioGroup radioGroup, int clickedId) {
View radioButtonClicked = radioGroup.findViewById(clickedId);
if (radioButtonClicked != null) {
Integer pos = (Integer) radioButtonClicked.getTag();
Log.d(TAG, "onCheckedChanged RadioGroup clicked position: " + pos);
for (int i = 0; i < mLayoutsDataContained.size(); i++) {
View view = mLayoutsDataContained.get(i);
if (pos.intValue() == i) {
view.setVisibility(View.VISIBLE);
} else view.setVisibility(View.GONE);
}
}
}
});
You must have misread the API. Second parameter is NOT position. It's the id, of the button. There are several solutions to your problem.
Adding id, based on position:
//not recommended way, as activity.findViewById uses this id, I GUESS something wrong may happen
private int currId;
...
b.setId(currId);
currId++;
container.addView(b);
Adding a tag:
//recommended
b.setTag(mAgrupacion);
container.addView(b);
//then...
AgrupacionSios tag = (AgrupacionSios) b.getTag();
//do action based on tag information
Use this.
for (int i = 0; i < mLayoutsDataContained.size(); i++) {
if (i == pos) {
mLayoutsDataContained.get(i - 1).setVisibility(View.VISIBLE);
} else {
mLayoutsDataContained.get(i - 1).setVisibility(View.GONE);
}
}

android:ellipsize="end" , "..." centered vertically

I have a weird problem, for some reason the android:ellipsize="end" works, but added the point in the middle of the text == centered vertically instead of being aligned to baseline:
I checked for any "center" properties, but there is none of those:
Update:
This is the XML part:
<com.citylifeapps.cups.customviews.CarmelaTextView
android:id="#+id/venue_address"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toLeftOf="#+id/venue_distance"
android:layout_toRightOf="#+id/venue_name"
android:gravity="left"
android:text="#string/placeholder_venue_address"
android:textColor="#color/cups_white"
android:textSize="20sp"
android:textStyle="bold"
android:ellipsize="end"
android:singleLine="true"
android:scrollHorizontally="true"
android:layout_alignBaseline="#+id/venue_name" />
And the custom TextView class:
public class CarmelaTextView extends TextView {
public CarmelaTextView(Context context, AttributeSet attrs) {
super(context, attrs);
setCarmelaTypeface(context);
}
public CarmelaTextView(Context context) {
super(context);
setCarmelaTypeface(context);
}
private void setCarmelaTypeface(Context context) {
if (this.isInEditMode()) return;
Typeface typeface = Typeface.createFromAsset(context.getAssets(), "carmela.ttf");
this.setTypeface(typeface);
}
}
further check shows that if I use a simple TextView the problem disappears,
but there is nothing in the custom TextView that will cause such a behavior.
Does anyone know why this might happen?
Thanks.
It looks like the problem lies within my custom font I'm using for this custom TextView, from the accepted answer here:
Why does TextView in single line elipsized with "end" show boxes?
I'm guessing that I'm facing the same problem but with a different result because the 3 dots (...) U+FEFF glyph for my font is different.
But still if some one found a solution that works for this issue I would be glad if he could share it.
I used this class to resolve this issue
public class EllipsizingTextView extends TextView {
private static final String ELLIPSIS = "...";
public interface EllipsizeListener {
void ellipsizeStateChanged(boolean ellipsized);
}
private final List<EllipsizeListener> ellipsizeListeners = new ArrayList<EllipsizeListener>();
private boolean isEllipsized;
private boolean isStale;
private boolean programmaticChange;
private String fullText;
private int maxLines = -1;
private float lineSpacingMultiplier = 1.0f;
private float lineAdditionalVerticalPadding = 0.0f;
public EllipsizingTextView(Context context) {
super(context);
}
public EllipsizingTextView(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.obtainStyledAttributes(attrs, new int[] { android.R.attr.maxLines });
setMaxLines(a.getInt(0, 1));
}
public EllipsizingTextView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
TypedArray a = context.obtainStyledAttributes(attrs, new int[] { android.R.attr.maxLines });
setMaxLines(a.getInt(0, 1));
}
public void addEllipsizeListener(EllipsizeListener listener) {
if (listener == null) {
throw new NullPointerException();
}
ellipsizeListeners.add(listener);
}
public void removeEllipsizeListener(EllipsizeListener listener) {
ellipsizeListeners.remove(listener);
}
public boolean isEllipsized() {
return isEllipsized;
}
#Override
public void setMaxLines(int maxLines) {
super.setMaxLines(maxLines);
this.maxLines = maxLines;
isStale = true;
}
public int getMaxLines() {
return maxLines;
}
#Override
public void setLineSpacing(float add, float mult) {
this.lineAdditionalVerticalPadding = add;
this.lineSpacingMultiplier = mult;
super.setLineSpacing(add, mult);
}
#Override
protected void onTextChanged(CharSequence text, int start, int before,
int after) {
super.onTextChanged(text, start, before, after);
if (!programmaticChange) {
fullText = text.toString();
isStale = true;
}
}
#Override
protected void onDraw(Canvas canvas) {
if (isStale) {
super.setEllipsize(null);
resetText();
}
super.onDraw(canvas);
}
private void resetText() {
int maxLines = getMaxLines();
String workingText = fullText;
boolean ellipsized = false;
if (maxLines != -1) {
Layout layout = createWorkingLayout(workingText);
if (layout.getLineCount() > maxLines) {
workingText = fullText.substring(0,
layout.getLineEnd(maxLines - 1)).trim();
while (createWorkingLayout(workingText + ELLIPSIS)
.getLineCount() > maxLines) {
workingText = workingText.substring(0,
workingText.length() - 1 - 1);
}
workingText = workingText + ELLIPSIS;
ellipsized = true;
}
}
if (!workingText.equals(getText())) {
programmaticChange = true;
try {
setText(workingText);
} finally {
programmaticChange = false;
}
}
isStale = false;
if (ellipsized != isEllipsized) {
isEllipsized = ellipsized;
for (EllipsizeListener listener : ellipsizeListeners) {
listener.ellipsizeStateChanged(ellipsized);
}
}
}
private Layout createWorkingLayout(String workingText) {
return new StaticLayout(workingText, getPaint(), getWidth()
- getPaddingLeft() - getPaddingRight(), Alignment.ALIGN_NORMAL,
lineSpacingMultiplier, lineAdditionalVerticalPadding, false);
}
#Override
public void setEllipsize(TruncateAt where) {
// Ellipsize settings are not respected
}
}

Categories

Resources