I have a custom View, which extends RelativeLayout.
This Relative Layout should consist of Rectangles which extends View, and this Rectangles should be painted by a bitmap and have a TextView in it..
But i cant add this Rectangles to the RelativeLayout so that they are below of each other.. it seems that the LayoutParams do not work.. Can you help me please?
Custom View extends RelativeLayout
public class DrawView extends RelativeLayout {
private Context context;
public DrawView(Context context, ArrayList<String> dragFieldText, ArrayList<String> targetFieldText, int height, int width) {
super(context);
this.context = context;
RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.FILL_PARENT, RelativeLayout.LayoutParams.FILL_PARENT);
Rectangle.count = 1;
for (int i = 0; i < targetFieldText.size(); i++) {
Rectangle targetRectangle = new Rectangle(context);
this.addView(targetRectangle);
}
Rectangle tmpRect = null;
for (int i = 0; i < dragFieldText.size(); i++) {
Rectangle dragRectangle = new Rectangle(context);
if(tmpRect != null){
tmpRect.setId(i);
lp.addRule(RelativeLayout.BELOW, tmpRect.getId());
dragRectangle.setLayoutParams(lp);
}
this.addView(dragRectangle);
tmpRect = dragRectangle;
}
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = View.MeasureSpec.getSize(widthMeasureSpec);
int height = View.MeasureSpec.getSize(heightMeasureSpec);
setMeasuredDimension(width, height);
}
Rectangle extends View
public class Rectangle extends View{
public Rectangle(Context context) {
super(context);
}
#Override
protected void onDraw(Canvas canvas) {
canvas.drawBitmap(getSomeBitmapImg(), new Matrix(), null);
}
Related
I have a problem with a disappearing of a custom view, which I've created programmatically and added to the item container (RelativeLayout), when I scroll it down and up. It builds successfully.
But after scrolling, I have this result in the same item - an empty container..
Custom View
public class WaveFormView extends View {
Paint p;
Paint pTop;
Paint pBottom;
Rect rect;
Rect rect1;
int [] amplitudeArray;
int amplitudeArraySize;
int leftPointX;
int rightPointX;
int index;
boolean isPlay;
public static final int MAX_AMPLITUDE = 32;//was 1000
public static final int SIZE_OF_BAR_WAVE_FORM = 98;
public static final int BAR_SIZE_AND_INTERVAL = 5;
private static final float screenDensity = Utils.getDisplayDensity();
public WaveFormView(Context context, int [] amplitudeArray, boolean isPlay, int index) {
super(context);
p = new Paint();
p.setColor(getResources().getColor(R.color.color_wave_from_grey));
pTop = new Paint();
pTop.setColor(getResources().getColor(R.color.color_wave_form_play_top));
pBottom = new Paint();
pBottom.setColor(getResources().getColor(R.color.color_wave_form_play_bottom));
this.amplitudeArray = amplitudeArray;
amplitudeArraySize = amplitudeArray.length;
this.isPlay = isPlay;
this.index = index;
}
#Override
protected void onDraw(Canvas canvas) {
if(!isPlay){
buildWaveForm(canvas);
}
else{
buildWaveFormWhenPlay(canvas);
}
}
private void buildWaveForm(Canvas canvas){
int oneDp = (int) (1*screenDensity);
int fourDp = (int) (4*screenDensity);
int amplitudeDp = 0;
int startBottomY = (int) (MAX_AMPLITUDE*screenDensity) + oneDp;
p.setColor(getResources().getColor(R.color.color_wave_from_grey));
for (int i = 0; i < amplitudeArraySize; i++){
p.setStyle(Paint.Style.FILL_AND_STROKE);
leftPointX += oneDp;
rightPointX = leftPointX+fourDp;
amplitudeDp = (int) (amplitudeArray[i]*screenDensity);
rect = new Rect(leftPointX, startBottomY, rightPointX, startBottomY + amplitudeDp);
rect1 = new Rect(leftPointX, startBottomY-oneDp-amplitudeDp, rightPointX, startBottomY-oneDp);
canvas.drawRect(rect, p);
canvas.drawRect(rect1, p);
leftPointX+=fourDp;
}
}
private void buildWaveFormWhenPlay(Canvas canvas){
...}
Adapter
public class PostTimeLineAdapter extends
RecyclerView.Adapter<PostTimeLineHolder> ...
#Override
public void onBindViewHolder(final PostTimeLineHolder holder, final int position) { ...
holder.homeItemContainerWaveForm.addView(new WaveFormView(context,bufferArray, false, -1));...}
Holder
public class PostTimeLineHolder extends RecyclerView.ViewHolder {
...
public RelativeLayout homeItemContainerWaveForm;
public PostTimeLineHolder(View itemView) {
super(itemView);
...
this.homeItemContainerWaveForm = (RelativeLayout) itemView.findViewById(R.id.home_item_sound_container_wave_forms);}}
Thanks for all your answers!
The reason of that weird behavior was in not zeroing after operator for those parameters: leftPointX and rightPointX, in the method buildWaveForm. If you set zeros for them after operator for, method buildWaveForm works acuratelly.
The custom View in my custom ViewGroup refuses to show the drawable given to it by calling setImageResource(). It is laid out as I need it, however, as you can see in this screenshot, it's empty:
Also, it won't react on an onClick event.
Here's the java code for my Activity
public class MainActivity extends Activity {
public static final String TAG = "MainActivity";
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
BattleShipsGameBoard gb = (BattleShipsGameBoard) findViewById(R.id.gameboard);
Tile tile = new Tile(this);
tile.setImageResource(R.drawable.tile_hit);
tile.setGameObjectType(BattleShipsGameBoard.LayoutParams.LAYOUT_TYPE_TILE);
tile.setPosition(new Point(50, 50));
tile.setWidth(90);
tile.setHeight(90);
gb.addView(tile);
}
}
and my custom view
public class Tile extends ImageView {
#SuppressWarnings("unused")
private static final String TAG = "Tile";
public int tag;
public int gameObjectType;
public Point position = new Point(0, 0);
public int mWidth = 1;
public int mHeight = 1;
public boolean isSelected = false;
public Tile(Context context) {
super(context);
setLayoutParams(new BattleShipsGameBoard.LayoutParams(
BattleShipsGameBoard.LayoutParams.WRAP_CONTENT,
BattleShipsGameBoard.LayoutParams.WRAP_CONTENT));
}
public Tile(Context context, AttributeSet attrs) {
super(context, attrs);
}
public Tile(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public void confirmChangesInLayout() {
BattleShipsGameBoard.LayoutParams lp = (BattleShipsGameBoard.LayoutParams) this
.getLayoutParams();
lp.setPosition(this.position);
lp.setWidth(this.mWidth);
lp.setHeight(this.mHeight);
setLayoutParams(lp);
invalidate();
requestLayout();
}
//... getters and setters, the setters all call confirmChangesInLayout()
}
my simple custom ViewGroup:
public class BattleShipsGameBoard extends ViewGroup {
public static class LayoutParams extends MarginLayoutParams {
public LayoutParams(Context c, AttributeSet attrs) {
super(c, attrs);
}
public LayoutParams(int width, int height) {
super(width, height);
}
public Point position = new Point(0, 0);
public int type = 0;
public int height = 0;
public int width = 0;
//getters and setters
}
public BattleShipsGameBoard(Context context) {
super(context);
}
public BattleShipsGameBoard(Context context, AttributeSet attrs) {
super(context, attrs);
}
public BattleShipsGameBoard(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
private float unitWidth;
private float unitHeight;
private int parentWidth;
private int parentHeight;
/**
* count of units the screen estate is divided by
*/
public static int unitCount = 100;
/**
* Rectangle in which the size of a child is temporarily stored
*/
private Rect mTmpChildRect = new Rect();
/**
* lays out children
*/
#Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
Log.d(TAG, "-------------STARTING LAYOUT, " + getChildCount() + " children -------------");
int count = getChildCount();
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (child.getVisibility() != GONE) {
LayoutParams lp = (LayoutParams) child.getLayoutParams();
Point pos = lp.getPosition();
int height = lp.getHeight();
int width = lp.getWidth();
measureChild(child, parentWidth, parentHeight);
mTmpChildRect.left = (int) ((pos.x - (width / 2)) * unitWidth);
mTmpChildRect.right = (int) ((pos.x + (width / 2)) * unitWidth);
mTmpChildRect.top = (int) ((pos.y + (height / 2)) * unitHeight);
mTmpChildRect.bottom = (int) ((pos.y - (height / 2)) * unitHeight);
child.layout(mTmpChildRect.left, mTmpChildRect.top, mTmpChildRect.right, mTmpChildRect.bottom);
Log.d(TAG,
}
}
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
parentHeight = MeasureSpec.getSize(heightMeasureSpec);
parentWidth = MeasureSpec.getSize(widthMeasureSpec);
unitHeight = parentHeight / unitCount;
unitWidth = parentWidth / unitCount;
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
if (child.getVisibility() != View.GONE) {
child.measure(widthMeasureSpec, heightMeasureSpec);
}
}
setMeasuredDimension(parentWidth, parentHeight);
}
/**
* Any layout manager that doesn't scroll will want this.
*/
#Override
public boolean shouldDelayChildPressedState() {
return false;
}
}
I just found the problem.
In the onLayout() method I mixed up mTmpChildRect.top and mTmpChildRect.bottom which is why it looked like it was laid out correctly but nothing could be drawn.
I would like to create a custom RelativeLayout that has two views in one row: one on the left side of the screen (android:layout_alignParentStart="true") and one on the right (android:layout_alignParentEnd="true"). The view on the right will grow toward the left view until it takes up all the space between the two views. Then it will move to a new line under the view on the left.
I have implemented a slightly modified version of Romain Guy's FlowLayout that extends RelativeLayout. However, this class seems to ignore the RelativeLayout's align properties and just sticks the views right next to each other. Is there a way to implement a such a layout that will anchor the views to the left and right?
FlowLayout class:
public class FlowLayout extends RelativeLayout {
private int mHorizontalSpacing;
private int mVerticalSpacing;
public FlowLayout(Context context) {
super(context);
}
public FlowLayout(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray attributes = context.obtainStyledAttributes(attrs, R.styleable.FlowLayout);
mHorizontalSpacing = attributes.getDimensionPixelSize(R.styleable
.FlowLayout_horizontalSpacing, 0);
mVerticalSpacing = attributes.getDimensionPixelSize(R.styleable
.FlowLayout_verticalSpacing, 0);
attributes.recycle();
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int width = 0;
int height = getPaddingTop();
int currentWidth = getPaddingStart();
int currentHeight = 0;
final int count = getChildCount();
for (int i = 0; i < count; i++) {
View child = getChildAt(i);
LayoutParams lp = (LayoutParams) child.getLayoutParams();
measureChild(child, widthMeasureSpec, heightMeasureSpec);
if (currentWidth + child.getMeasuredWidth() > widthSize) {
height += currentHeight + mVerticalSpacing;
currentHeight = 0;
width = Math.max(width, currentWidth);
currentWidth = getPaddingEnd();
}
int spacing = mHorizontalSpacing;
if (lp.spacing > -1) {
spacing = lp.spacing;
}
lp.x = currentWidth + spacing;
lp.y = currentHeight;
currentWidth += child.getMeasuredWidth();
currentHeight = Math.max(currentHeight, child.getMeasuredHeight());
}
width += getPaddingEnd();
height += getPaddingBottom();
setMeasuredDimension(resolveSize(width, widthMeasureSpec), resolveSize(height,
heightMeasureSpec));
}
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
final int count = getChildCount();
for (int i = 0; i < count; i++) {
View child = getChildAt(i);
LayoutParams lp = (LayoutParams) child.getLayoutParams();
child.layout(lp.x, lp.y, lp.x + child.getMeasuredWidth(), lp.y + child
.getMeasuredHeight());
}
}
#Override
protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
return p instanceof LayoutParams;
}
#Override
protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
return new LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout
.LayoutParams.WRAP_CONTENT);
}
#Override
protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
return new LayoutParams(p.width, p.height);
}
#Override
public RelativeLayout.LayoutParams generateLayoutParams(AttributeSet attrs) {
return new LayoutParams(getContext(), attrs);
}
public static class LayoutParams extends RelativeLayout.LayoutParams {
public int spacing;
public int x;
public int y;
public LayoutParams(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray attributes = context.obtainStyledAttributes(attrs, R.styleable
.FlowLayout_LayoutParams);
spacing = attributes.getDimensionPixelSize(R.styleable
.FlowLayout_LayoutParams_layoutSpacing, -1);
attributes.recycle();
}
public LayoutParams(int width, int height) {
super(width, height);
}
}
}
It turns out that rather than calculating the right view's new position yourself, you can change its LayoutParams and have the OS handle positioning for you. I created a custom layout that extends RelativeLayout and overrides the onMeasure() method. This will adjust the LayoutParams accordingly.
More specifically:
Call the super method then find the widths of the two views and their parent in onMeasure(). Use these to figure out if the right view will overlap the left view. If so, change the right view's layout_alignParentEnd="true" property to be layout_alignParentStart="true" and give it the layout_below="#id/left_view" property. Do the opposite when there will be no overlap. Call the super method again to have the OS remeasure the views for you.
The layout class:
public class WrappingLayout extends RelativeLayout {
private TextView leftView;
private EditText rightView;
//Use this to prevent unnecessarily adjusting the LayoutParams
//when the right view is already in the correct position
private boolean isMultiline = false;
public WrappingLayout(Context context) {
super(context);
LayoutInflater inflater = LayoutInflater.from(context);
inflater.inflate(R.layout.wrapping_layout, this);
leftView = (TextView) findViewById(R.id.left_view);
rightView = (EditText) findViewById(R.id.right_view);
}
public WrappingLayout(Context context, AttributeSet attrs) {
super(context, attrs);
LayoutInflater inflater = LayoutInflater.from(context);
inflater.inflate(R.layout.wrapping_layout, this);
leftView = (TextView) findViewById(R.id.left_view);
rightView = (EditText) findViewById(R.id.right_view);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//Call first to make sure the views' initial widths have been set
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int screenWidth = getMeasuredWidth();
int leftViewWidth = getPaddingStart() + leftView.getMeasuredWidth() + leftView.getPaddingEnd();
int rightViewWidth = getPaddingEnd() + rightView.getMeasuredWidth() + rightView.getPaddingStart();
LayoutParams rightViewParams = (LayoutParams) rightView.getLayoutParams();
if (!isMultiline && rightViewWidth + leftViewWidth > screenWidth) {
isMultiline = true;
rightViewParams.addRule(BELOW, R.id.left_view);
rightViewParams.removeRule(ALIGN_PARENT_END);
rightViewParams.addRule(ALIGN_PARENT_START);
//Call again here to adjust dimensions for new params
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
} else if (isMultiline && rightViewWidth + leftViewWidth < screenWidth) {
isMultiline = false;
rightViewParams.removeRule(BELOW);
rightViewParams.addRule(ALIGN_PARENT_END);
rightViewParams.removeRule(ALIGN_PARENT_START);
//Call again here to adjust dimensions for new params
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
}
The layout XML:
<?xml version="1.0" encoding="utf-8"?>
<merge
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="#id/left_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_centerVertical="true"/>
<EditText
android:id="#id/right_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:gravity="center"
android:text="#string/hello"/>
</merge>
/* Code snipet from the MainActivity */
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
/* Linear layout */
LinearLayout layout = (LinearLayout)findViewById(R.id.main_container);
/* Creating two similar custom views */
CustomView row1 = new CustomView(this); /* First row */
CustomView row2 = new CustomView(this); /* second row */
layout.add(row1,0);
layout.add(row2,1);
}
CustomView.java
public class CustomView extends View {
public final String TAG = "CustomView";
Context context;
private Drawable backgroundDrawable;
Rect backGroundDrawableRect;
public TextMessageView(Context context) {
super(context);
this.context = context;
backgroundDrawable = ContextCompat.getDrawable(context,R.drawable.background_drawable);
}
#Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
backGroundDrawableRect.left = left;
backGroundDrawableRect.right = right;
backGroundDrawableRect.bottom = bottom;
backGroundDrawableRect.top = top;
backgroundDrawable.setBounds(backGroundDrawableRect);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
/* 100 pixel height for the view */
setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec),100);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if(backgroundDrawable != null) {
backgroundDrawable.draw(canvas);
}
}
}
activity_main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:id="#+id/main_container"
android:layout_width="match_parent"
android:layout_height="match_parent">
</LinearLayout>
The problem is that the view added in the second row of the linear layout is not getting displayed. During debugging, when i enabled the 'show layout bound ' in developers option , I found that the view in the second row was taking its required space but the content which is a drawable was not getting displayed.
The Canvas coordinates are automatically adjusted to the view's position so you need to set the left and top bounds of your Drawable to zero instead:
backGroundDrawableRect.left = 0;
backGroundDrawableRect.right = right - left;
backGroundDrawableRect.bottom = bottom - top;
backGroundDrawableRect.top = 0;
Change in CustomView
public class CustomView extends View {
public final String TAG = "CustomView";
Context context;
private Drawable backgroundDrawable;
Rect backGroundDrawableRect;
public CustomView(Context context) {
super(context);
this.context = context;
backgroundDrawable = ContextCompat.getDrawable(context,R.drawable.ic_launcher);
}
#Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
/* backGroundDrawableRect.left = left;
backGroundDrawableRect.right = right;
backGroundDrawableRect.bottom = bottom;
backGroundDrawableRect.top = top;
backgroundDrawable.setBounds(backGroundDrawableRect);
*/
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
/* 100 pixel height for the view */
setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec),100);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if(backgroundDrawable != null) {
backgroundDrawable.draw(canvas);
}
}
and your launcher activity do this change
public class Test extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.test1);
/* Linear layout */
LinearLayout layout = (LinearLayout) findViewById(R.id.main_container);
/* Creating two similar custom views */
CustomView row1 = new CustomView(getApplicationContext()); /* First row */
row1.setBackgroundColor(Color.RED);
CustomView row2 = new CustomView(getApplicationContext()); /* Second row */
row2.setBackgroundColor(Color.GREEN);
layout.addView(row1);
layout.addView(row2);
}
}
I'm using a custom view MaskCustomView by subclassing View. The width/height layout params for the custom view are both WRAP_CONTENT.
What happens is the custom view in the LinearLayout takes all the space. I can see that using uiautomatorViewer :
Code is :
public class MaskImageView extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.mask_image);
LinearLayout container = (LinearLayout) findViewById(R.id.maskID);
MaskCustomView maskView = new MaskCustomView(this);
maskView.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT));
container.addView(maskView);
}
public class MaskCustomView extends View {
private Bitmap bp;
private Paint p;
public MaskCustomView(Context context) {
super(context);
bp = BitmapFactory.decodeResource(getResources(), R.drawable.apple);
p = new Paint();
}
#Override
public void onDraw(Canvas canvas) {
canvas.drawBitmap(bp, 0, 0 , p);
super.onDraw(canvas);
}
}
}
xml layout :
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:id="#+id/maskID"
android:layout_width="match_parent"
android:layout_height="match_parent">
Shouldn't just wrap content and don't use whole space for both height and width ?
Try to make your MaskCustomView override onMeasure:
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if(bp == null) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
} else {
setMeasuredDimension(MeasureSpec.makeMeasureSpec(bp.getWidth(), MeasureSpec.MODE_CONSTANT), MeasureSpec.makeMeasureSpec(bp.getHeight(), MeasureSpec.MODE_CONSTANT);
}
}