I want to create a vertical seekbar with progress displaying near to thumb.
I created vertical seekbar by overriding Seekbar class and rotate the canvas. But I don't know how to make the text near to thumb.
#Override
protected final void onDraw(#NonNull final Canvas c) {
if (null == mPaint) {
mPaint = new Paint();
mPaint.setColor(Color.BLACK);
mPaint.setTextSize(50);
}
c.rotate(ROTATION_ANGLE);
c.translate(-getHeight(), 0);
super.onDraw(c);
Rect rect = getThumb().getBounds();
c.drawText(getProgress()+"%", rect.exactCenterX(), rect.exactCenterY(), mPaint);
}
So the problem is If I drawText like this the text also rotated.
So how to fix this?
Meanwhile, I tried some custom View implementation and I am a noob on that.
Update to Khemraj's answer
Actually changing the thumb is the simple trick as said by Khemraj. But the problem is when rotating the canvas all its contents will rotate(simple logic). When updating the thumb also will reflect this problem. So the simple thing is to make a rotated CustomTextView.
Method to make thumb
public Drawable getThumb(int progress) {
int width = getWidth();
((TextView) mThumbView.findViewById(R.id.tvProgress)).setText(String.format(Locale.getDefault(), "%d%%", progress));
mThumbView.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
Bitmap bitmap = Bitmap.createBitmap(mThumbView.getMeasuredWidth(), mThumbView.getMeasuredHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
mThumbView.layout(0, 0, mThumbView.getMeasuredWidth(), mThumbView.getMeasuredHeight());
mThumbView.draw(canvas);
return new BitmapDrawable(getResources(), bitmap);
}
The layout
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout 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"
android:gravity="center">
<ImageView
android:layout_marginTop="30dp"
android:id="#+id/imageView6"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="#drawable/seek_thumb_gray"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.samsung.lighting.presentation.ui.custom_views.RotatedTextView
android:id="#+id/tvProgress"
android:layout_width="wrap_content"
android:layout_height="40dp"
android:gravity="center"
android:text="20%"
android:textColor="#000000"
android:textSize="12sp"
app:angle="90"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/imageView6" />
</android.support.constraint.ConstraintLayout>
The RotatedTextView
public class RotatedTextView extends AppCompatTextView {
private int mRotationAngle = 90;
#Override
protected void onDraw(Canvas canvas) {
if (mRotationAngle == 90) {
canvas.rotate(mRotationAngle);
canvas.translate(0, -getWidth());
} else if (mRotationAngle == -90) {
canvas.rotate(mRotationAngle);
canvas.translate(-getHeight(), 0);
}
super.onDraw(canvas);
}
}
So first we will rotate the Text to 90 or -90 degrees and set as thumb to VerticalSeekBar and in vertical seekbar rotate the canvas to 90 or -90 degrees. So finally we will get the actual result
Here I posted the working example.
Thanks, Khemraj
Related
I have a fragment that displays some images on 30% of the screen. In case when image.height is bigger than image.width, I wanted to rotate the image 90 degrees. However, after that operation, the image does not fully fill the fragment - "new" width after rotation matches previous height, so the image occupies only some part in the middle of the fragment. How can I make it a matching fragment? What I'm doing wrong or missing?
This is how it looks like without rotation:
This is after rotation:
And that's what I want to have:
Here is the code:
#Override
public void onViewCreated(#NonNull View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
setImageFittedToDisplay(view);
}
public void setImageFittedToDisplay(View view) {
int height = getDisplayMetrics().heightPixels;
ViewGroup.LayoutParams params = view.getLayoutParams();
params.height = ((Double) (height * 0.3)).intValue();
view.setLayoutParams(params);
ImageView image = view.findViewById(R.id.picture);
image.setDrawingCacheEnabled(true);
int w = image.getDrawable().getIntrinsicWidth();
int h = image.getDrawable().getIntrinsicHeight();
if(h > w) {
image.setRotation(90f);
}
ViewGroup.LayoutParams imageParams = image.getLayoutParams();
imageParams.height = ActionBar.LayoutParams.WRAP_CONTENT;
imageParams.width = ActionBar.LayoutParams.WRAP_CONTENT;
image.setLayoutParams(imageParams);
}
and here is .xml of the fragment:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/APOD_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
xmlns:app="http://schemas.android.com/apk/res-auto">
<ImageView
android:id="#+id/picture"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="#drawable/cool_pic"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<View
android:id="#+id/separator"
android:layout_width="match_parent"
android:layout_height="10dp"
android:background="#color/colorSeparator"
app:layout_constraintBottom_toBottomOf="#+id/picture"
app:layout_constraintTop_toBottomOf="#+id/picture" />
</androidx.constraintlayout.widget.ConstraintLayout>
I have a custom ConstraintLayout class (Card.java) which overrides the onDraw() method to draw a hexagon in his background. On the foreground i try to have three TextViews to display three numbers.
For this I inflate a card.xml in the constructor of Card. The TextViews are displayed, but not at the right position. They should match the width and height of the Card and then position itself to the top-left and top-right corner and one to the bottom of the Card. But they do something like shrink itself and go to the top-left corner.
I have tried to change the root element of card.xml to "merge" instead of "...ConstraintLayout" but this doesn't change anything.
I also tried to use Guidelines to position the TextViews relative to its width. I try to prevent the use of fixed margins, so the Text is always at the right place, also when the size of the Card changes.
Card.java:
public class Card extends ConstraintLayout {
private int mSize;
private Path mHexagon;
private Paint mPaintHexagon;
private TextView mT1, mT2, mT3;
public Card(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
inflate(context, R.layout.card, this);
// Numbers
mT1 = findViewById(R.id.num1);
mT2 = findViewById(R.id.num2);
mT3 = findViewById(R.id.num3);
// Hexagon
mSize = Field.getHexSize(); // Size is used to calculate the
setPath();
mPaintHexagon = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaintHexagon.setColor(0x50888888);
mPaintHexagon.setStyle(Paint.Style.FILL);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawPath(mHexagon, mPaintHexagon);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = 2 * mSize;
int height = (int) (Math.sqrt(3) * mSize);
Log.d(TAG, "Measure w:" + width + " h:" + height);
setMeasuredDimension(width, height);
}
}
card.xml:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
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:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/num2"
android:textAppearance="#style/TextAppearance.AppCompat.Large"
android:text="2"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginTop="8dp"
app:layout_constraintStart_toStartOf="parent"
android:layout_marginStart="8dp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/num1"
android:textAppearance="#style/TextAppearance.AppCompat.Large"
android:text="1"
app:layout_constraintStart_toEndOf="#+id/num2"
app:layout_constraintEnd_toStartOf="#+id/num3"
app:layout_constraintBottom_toBottomOf="parent"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/num3"
android:textAppearance="#style/TextAppearance.AppCompat.Large"
android:text="3"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginEnd="8dp"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginTop="8dp"/>
</android.support.constraint.ConstraintLayout>
activity_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity"
android:background="#color/colorAccentDark"
android:padding="5dp">
<de.portugall.markus.takeiteasy.Card
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:id="#+id/card"/>
</android.support.constraint.ConstraintLayout>
Screenshot Card in Layout-Debug mode
'onMeasure()` has some rules that you are not strictly following. I have seen these rules broken with impunity, but I think that you are being caught, but we will push on.
In onMeasure() you are setting the height and width of the custom layout but ConstraintLayout still understands the layout as wrap_content. You will need to set the layout params to the new height and width. Add the following code to the end of onMeasure():
// Although we have measured the layout, we need to tell ConstraintLayout in the
// LayoutParams that the size is not longer "wrap_content".
ViewGroup.LayoutParams lp = getLayoutParams();
lp.width = width;
lp.height = height;
setLayoutParams(lp);
The second issue that you have is that you are adding a ConstraintLayout (card.xml) to a ConstraintLayout (your custom layout), but you are not setting the constraints. In the constructor for Card.java, add the following to set the constraints:
ConstraintLayout layout = (ConstraintLayout) inflate(context, R.layout.card, this);
// We have added R.layout.card to a ConstraintLayout (this custom layout), so we need
// to make sure that it is constrained properly.
ConstraintSet cs = new ConstraintSet();
cs.clone(layout);
cs.connect(R.id.layout, ConstraintSet.START, ConstraintSet.PARENT_ID, ConstraintSet.START);
cs.connect(R.id.layout, ConstraintSet.TOP, ConstraintSet.PARENT_ID, ConstraintSet.TOP);
cs.connect(R.id.layout, ConstraintSet.END, ConstraintSet.PARENT_ID, ConstraintSet.END);
cs.connect(R.id.layout, ConstraintSet.BOTTOM, ConstraintSet.PARENT_ID, ConstraintSet.BOTTOM);
cs.applyTo(layout);
You will need to change the height and width of the layout in card.xml to 0dp. match_parent is never appropriate in ConstraintLayout.
This is a pictorial description of what is happening:
On a related note, you should consider using the merge facility to avoid nested ConstraintLayouts as other have mentioned.
I am trying to reconcile the following two things:
A) I want a precise, uniform, and clean UI with several identically sized buttons that correspond exactly to the underlying 'grid cells' -- A UI that will look as similar as possible (proportionally to screen size) across as many Android devices as possible.
B) On Android, the screen dimensions (aspect ratio and actual pixel numbers) of the user's device are unknown (to the app) until runtime.
My solution to this was to: (there is a code example below!)
1) Lock the app to portrait mode,
2) Do not define anything in static/absolute terms like dp,px, etc. and instead conceptualize a 'basic unit of measure' that is a function of screen height -- 0.08% in my case -- and base everything off of that.
3) Set horizontal guidelines within a ConstraintLayout whose positions are expressed as a percentage of parent (screen) height.
4) Make all buttons use this 'basic unit' as their height and width by setting their XML layout_constraintDimensionRatio attribute to "1:1" and using the guidelines above (see step 3),
5) Accomplish positioning and dimensions of all views by using constraints to either these guidelines, the parent's bounds, or one additional vertical guideline at 50% of screen width.
The problem is that depending on the pixel height of the screen (whether it happens to be odd or even... or maybe other factors), the dimensions of a view/button, (and thus the paths drawn inside it) constrained between one pair of guidelines does not exactly match those of another view drawn between some other pair... even though the distance between both pairs of guidelines should be the same percentage of parent height. :)
Here is an example showing the Nexus 4 emulator:
At first I thought the problem was simply due to rounding 'error' during Android's dimension calculations, but then why would the view not be square even though they are prescribed the 1:1 ratio attribute?
The only solutions I can think of would be:
A) To do the layout programatically instead of with XML... and set the guideline positions as exact pixel locations instead of percentages, and answer the question, "what is 0.08 x screen height?" myself... making the appropriate corrections to compensate for 'indivisible' screen heights.
B) Override onLayout() in the custom views and "force" their dimensions to be consistent... but then this would defeat the purpose of guidelines. :(
But I'm really hoping there is an easier solution than A or B.
(I know someone is going to suggest GridLayout, but it's not an option, for a few reasons... one of which is that in GridLayout, views inside cells must be set to wrap_content... which means the paths they draw cannot be generated relative to parent at runtime).
Thanks for any other suggestions, though.
Code Example:
I whipped up a simple 'minimal example' below that should be easy to reconstruct in Android Studio. The logs will reveal the issue if it's not immediately apparent.
XML layout:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/rootView"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<android.support.constraint.Guideline
android:id="#+id/guidelineHorizontalTop"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintGuide_percent="0.08" />
<android.support.constraint.Guideline
android:id="#+id/guidelineHorizontalBottom"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintGuide_percent="0.92" />
<android.support.constraint.Guideline
android:id="#+id/guidelineHorizontalCenter1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintGuide_percent="0.38" />
<android.support.constraint.Guideline
android:id="#+id/guidelineHorizontalCenter2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintGuide_percent="0.46" />
<android.support.constraint.Guideline
android:id="#+id/guidelineHorizontalCenter3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintGuide_percent="0.54" />
<android.support.constraint.Guideline
android:id="#+id/guidelineHorizontalCenter4"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintGuide_percent="0.62" />
<com.example.boober.stack_aliasingproblem.CustomButton
android:id="#+id/buttonTopLeft"
android:layout_width="0dp"
android:layout_height="0dp"
android:tag="buttonTopLeft"
app:layout_constraintBottom_toTopOf="#+id/guidelineHorizontalTop"
app:layout_constraintDimensionRatio="1:1"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.example.boober.stack_aliasingproblem.CustomButton
android:id="#+id/buttonTopRight"
android:layout_width="0dp"
android:layout_height="0dp"
android:tag="buttonTopRight"
app:layout_constraintBottom_toTopOf="#+id/guidelineHorizontalTop"
app:layout_constraintDimensionRatio="1:1"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.example.boober.stack_aliasingproblem.CustomButton
android:id="#+id/buttonBottomLeft"
android:layout_width="0dp"
android:layout_height="0dp"
android:tag="buttonBottomLeft"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintDimensionRatio="1:1"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="#+id/guidelineHorizontalBottom" />
<com.example.boober.stack_aliasingproblem.CustomButton
android:id="#+id/buttonBottomRight"
android:layout_width="0dp"
android:layout_height="0dp"
android:tag="buttonBottomRight"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintDimensionRatio="1:1"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="#+id/guidelineHorizontalBottom" />
<com.example.boober.stack_aliasingproblem.CustomButton
android:id="#+id/buttonMiddle"
android:layout_width="0dp"
android:layout_height="0dp"
android:tag="buttonMiddle"
app:layout_constraintBottom_toBottomOf="#id/guidelineHorizontalCenter3"
app:layout_constraintDimensionRatio="1:1"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="#id/guidelineHorizontalCenter2" />
<com.example.boober.stack_aliasingproblem.CustomButton
android:id="#+id/buttonMiddleTopLeft"
android:layout_width="0dp"
android:layout_height="0dp"
android:tag="buttonMiddleTopLeft"
app:layout_constraintBottom_toBottomOf="#id/guidelineHorizontalCenter2"
app:layout_constraintDimensionRatio="1:1"
app:layout_constraintEnd_toStartOf="#id/buttonMiddle"
app:layout_constraintTop_toTopOf="#id/guidelineHorizontalCenter1" />
<com.example.boober.stack_aliasingproblem.CustomButton
android:id="#+id/buttonMiddleTopRight"
android:layout_width="0dp"
android:layout_height="0dp"
android:tag="buttonMiddleTopRight"
app:layout_constraintBottom_toBottomOf="#id/guidelineHorizontalCenter2"
app:layout_constraintDimensionRatio="1:1"
app:layout_constraintStart_toEndOf="#id/buttonMiddle"
app:layout_constraintTop_toTopOf="#id/guidelineHorizontalCenter1" />
</android.support.constraint.ConstraintLayout>
MainActivity.java:
public class MainActivity extends AppCompatActivity {
CustomButton buttonTopLeft;
CustomButton buttonTopRight;
CustomButton buttonMiddle;
CustomButton buttonMiddleTopLeft;
CustomButton getButtonMiddleTopRight;
CustomButton buttonBottomLeft;
CustomButton buttonBottomRight;
CustomButton[] arrayOfCustomButtons;
ConstraintLayout rootView;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
buttonTopLeft = findViewById(R.id.buttonTopLeft);
buttonTopRight = findViewById(R.id.buttonTopRight);
buttonBottomLeft = findViewById(R.id.buttonBottomLeft);
buttonBottomRight = findViewById(R.id.buttonBottomRight);
buttonMiddle = findViewById(R.id.buttonMiddle);
buttonMiddleTopLeft = findViewById(R.id.buttonMiddleTopLeft);
getButtonMiddleTopRight = findViewById(R.id.buttonMiddleTopRight);
arrayOfCustomButtons = new CustomButton[]{buttonTopLeft, buttonTopRight, buttonBottomLeft,
buttonBottomRight, buttonMiddle, buttonMiddleTopLeft, getButtonMiddleTopRight};
rootView = findViewById(R.id.rootView);
for (final CustomButton cb : arrayOfCustomButtons) {
cb.setClickable(true);
cb.post(new Runnable() {
#Override
public void run() {
Log.i("XXX", "width of: " + cb.getTag() + " is: "
+ cb.getMeasuredWidth());
}
});
}
rootView.post(new Runnable() {
#Override
public void run() {
Log.i("XXX", "height of rootView is: " + rootView.getMeasuredHeight());
}
});
}
}
CustomButton.java:
public class CustomButton extends View {
Path myOutlinePath;
Paint myThinPaintBrush;
Paint myThickPaintBrush;
boolean isHighlighted = false;
public CustomButton(Context context) {
super(context);
init();
}
public CustomButton(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public CustomButton(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
#Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
float measuredWidth = getMeasuredWidth();
Log.i("XXX", "measured WIDTH Of " + this.getTag() + " is: " + measuredWidth);
Log.i("XXX", "measured HEIGT Of " + this.getTag() + " is: " + getMeasuredHeight());
Log.i("XXX", "\n ");
generateMyOutline(measuredWidth);
myThinPaintBrush.setStrokeWidth(measuredWidth/12);
myThickPaintBrush.setStrokeWidth(measuredWidth/6);
}
private void generateMyOutline(float W) {
Path path = new Path();
path.moveTo(0,0);
path.lineTo(W, 0);
path.lineTo(W, W);
path.lineTo(0, W);
path.lineTo(0,0);
myOutlinePath = path;
}
private void init() {
myOutlinePath = new Path();
myThinPaintBrush = new Paint();
myThinPaintBrush.setAntiAlias(false); // setting this to true does not solve the problem.
myThinPaintBrush.setStyle(Paint.Style.STROKE);
myThinPaintBrush.setStrokeCap(Paint.Cap.ROUND);
myThickPaintBrush = new Paint();
myThickPaintBrush.setAntiAlias(false);
myThickPaintBrush.setStyle(Paint.Style.STROKE);
myThickPaintBrush.setStrokeCap(Paint.Cap.ROUND);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
if (this.isClickable()) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
isHighlighted = true;
invalidate();
break;
case MotionEvent.ACTION_UP:
isHighlighted = false;
invalidate();
break;
case MotionEvent.ACTION_CANCEL:
isHighlighted = false;
invalidate();
break;
}
}
return super.onTouchEvent(event);
}
#Override
protected void onDraw(Canvas canvas) {
canvas.drawPath(myOutlinePath, myThinPaintBrush);
if (isHighlighted) {
canvas.drawPath(myOutlinePath, myThickPaintBrush);
}
super.onDraw(canvas);
}
}
I would go for the middle ground: Use your XML layout as is and make adjustments programmatically to the guideline positions. The following code converts percentage guidelines to fixed position guidelines by computing a new layout height that is a multiple of 8% of the height of the initial layout.
All sizes are computed correctly except for the bottom squares that tend to be larger. This can be easily corrected based upon your actual requirements (more important to be at the bottom or a certain distance from the other squares, for instance.)
MainActivity.jav
public class MainActivity extends AppCompatActivity {
CustomButton buttonTopLeft;
CustomButton buttonTopRight;
CustomButton buttonMiddle;
CustomButton buttonMiddleTopLeft;
CustomButton getButtonMiddleTopRight;
CustomButton buttonBottomLeft;
CustomButton buttonBottomRight;
CustomButton[] arrayOfCustomButtons;
ConstraintLayout rootView;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
buttonTopLeft = findViewById(R.id.buttonTopLeft);
buttonTopRight = findViewById(R.id.buttonTopRight);
buttonBottomLeft = findViewById(R.id.buttonBottomLeft);
buttonBottomRight = findViewById(R.id.buttonBottomRight);
buttonMiddle = findViewById(R.id.buttonMiddle);
buttonMiddleTopLeft = findViewById(R.id.buttonMiddleTopLeft);
getButtonMiddleTopRight = findViewById(R.id.buttonMiddleTopRight);
rootView = findViewById(R.id.rootView);
rootView.post(new Runnable() {
#Override
public void run() {
int rootViewHeight = rootView.getMeasuredHeight();
Log.i("XXX", "height of rootView is: " + rootViewHeight);
int segHeight = (int) (rootViewHeight * 0.08f);
adjustGuideline(R.id.guidelineHorizontalTop, segHeight);
adjustGuideline(R.id.guidelineHorizontalCenter1, segHeight);
adjustGuideline(R.id.guidelineHorizontalCenter2, segHeight);
adjustGuideline(R.id.guidelineHorizontalCenter3, segHeight);
adjustGuideline(R.id.guidelineHorizontalCenter4, segHeight);
adjustGuideline(R.id.guidelineHorizontalBottom, segHeight);
arrayOfCustomButtons = new CustomButton[]{buttonTopLeft, buttonTopRight, buttonBottomLeft,
buttonBottomRight, buttonMiddle, buttonMiddleTopLeft, getButtonMiddleTopRight};
rootView = findViewById(R.id.rootView);
for (final CustomButton cb : arrayOfCustomButtons) {
cb.setClickable(true);
cb.post(new Runnable() {
#Override
public void run() {
Log.i("MainActivity", "<<<< width of: " + cb.getTag() + " is: "
+ cb.getMeasuredWidth());
}
});
}
}
});
}
private void adjustGuideline(int guideLineId, int segHeight) {
Guideline gl = (Guideline) findViewById(guideLineId);
ConstraintLayout.LayoutParams lp = ((ConstraintLayout.LayoutParams) gl.getLayoutParams());
gl.setGuidelineBegin((int) (segHeight * lp.guidePercent / 0.08f));
gl.setGuidelinePercent(-1f);
}
}
I have a custom view that extends SurfaceView overlaying the rest of my interface, it works on emulators and when the debugger is connected on my phone, but but when the phone is running on battery the view never clears.
public class CustomView extends SurfaceView {
private final Paint paint;
private final SurfaceHolder holder;
private final Context context;
private float strokeWidth = 4;
private boolean canvasAlreadyLocked = false;
public CustomView(Context viewContext, AttributeSet attrs)
{
super(viewContext, attrs);
Log.i("CustomView", "CustomView create context & attrs");
holder = getHolder();
context = viewContext;
paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setColor(Color.WHITE);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(strokeWidth);
drawLine();
setZOrderOnTop(true);
holder.setFormat(PixelFormat.TRANSPARENT);
}
public void resume() {
Log.i("CustomView", "Resume the customview display.");
setZOrderOnTop(true);
holder.setFormat(PixelFormat.TRANSPARENT);
}
#Override
public void onAttachedToWindow(){
super.onAttachedToWindow();
setZOrderOnTop(true);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
}
protected void drawLine() {
if (!canvasAlreadyLocked) {
invalidate();
if (holder.getSurface().isValid()) {
try {
final Canvas canvas = holder.lockCanvas();
canvasAlreadyLocked = true;
if (canvas != null) {
canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
paint.setColor(Color.BLACK);
paint.setStrokeWidth(strokeWidth * 2);
canvas.drawLine(0, getY(), getWidth(), getY(), paint);
paint.setColor(Color.WHITE);
paint.setStrokeWidth(strokeWidth);
canvas.drawLine(0, getY(), getWidth(), getY(), paint);
holder.unlockCanvasAndPost(canvas);
canvasAlreadyLocked = false;
}
}
catch (IllegalArgumentException iae)
{
Log.w("CustomView", "Exception trying to lock canvas: "+iae.getMessage());
Log.getStackTraceString(iae);
}
}
}
}
private float getY() {
getHeight()/2;
}
}
I'm aware that some of the calls here are redundant - that is mostly the legacy of trying lots of different things to try to make it work. You will notice that I have already done everything recommended in this answer.
The layout works like this:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.custom.CustomViewApp">
<FrameLayout
android:id="#+id/control"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignParentBottom="true"
android:layout_alignParentStart="true">
<com.custom.AutoFitTextureView
android:id="#+id/texture"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_alignParentTop="true" />
<ImageButton
android:id="#+id/gpsNotification"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="#drawable/gps_unfixed"
android:layout_gravity="right"
android:tint="#color/gps_unfixed"
android:background="#null" />
<ProgressBar
android:id="#+id/camera_spinner"
style="?android:attr/progressBarStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal|center_vertical"
android:gravity="center"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:visibility="invisible"
/>
<com.custom.CustomView
android:id="#+id/custom_view"
android:background="#color/transparent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_alignParentTop="true" />
</FrameLayout>
</FrameLayout>
This is pulled in from a ViewFragment:
#Override
public void onViewCreated(final View view, Bundle savedInstanceState) {
mTextureView = (AutoFitTextureView) view.findViewById(R.id.texture);
gpsNotification = (ImageButton) view.findViewById(R.id.gpsNotification);
customView = (CustomView) view.findViewById(R.id.custom_view);
spinner = (ProgressBar) view.findViewById(R.id.camera_spinner);
spinner.setVisibility(VISIBLE);
}
I have tried to simplify this as far as I can and obviously there is a lot more happening in this scenario, but hopefully this is enough to indicate where the problem might be coming from.
The AutoFitTextureView is displaying the view from the camera.
When I run it in an emulator, everything displays as expected,
regardless of the battery settings.
When I run it on my phone connected by USB everything displays as expected.
When I run it on my phone disconnected, the view will usually, but not always, show as plain black - the AutoFitTextureView is completely obscured, but the line on the CustomView is drawn. The other components are visible, which leads me to suspect there is a problem with when or how I call setZOrderOnTop().
If I hit a breakpoint in the fragment and set the visibility of the CustomView to INVISIBLE I can immediately see everything else, but as you might expect I lose the overlay. Manually calling setZOrderOnTop at that point doesn't seem to change anything.
When it is running correctly, I can examine the structure with the Layout Inspector tool, but when the SurfaceView is opaque the Layout Inspector raises an Unexpected Error: Empty View Hierarchy message. I haven't been able to locate a corresponding error message in Logcat.
How do I ensure my SurfaceView-derived View is always transparent? If I can't guarantee that, is there any way I can test whether it currently is transparent so that I can intercede if it is not?
I think I have now solved this and the problem was that I was trying to put a SurfaceView derived type over a TextureView derived type. Switching out the AutofitTextureView for another SurfaceView showing the camera preview seems to have done the trick.
I am making an Android app and attempting to programatically draw some clickable, rectangular blocks with text on one of two custom views contained in a ScrollLayout. The XML for my ScrollLayout looks like this:
<ScrollView
android:layout_width="match_parent"
android:id="#+id/calendarGridContainerView"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:layout_below="#+id/employeeSpinnerContainer"
android:paddingBottom="72dp"
android:layout_height="match_parent"
android:overScrollMode="never"
android:fillViewport="true"
android:elevation="1dp">
<LinearLayout
android:orientation="horizontal"
android:layout_width="wrap_content"
android:id="#+id/schedule_view_container"
android:layout_height="wrap_content">
<FrameLayout
android:layout_height="wrap_content"
android:id="#+id/schedule_time_frame"
android:layout_width="100dp"
android:layout_alignParentTop="false"
android:layout_weight="0.5">
<include
layout="#layout/schedule_time_view"
android:layout_height="wrap_content"
android:layout_width="100dp" />
</FrameLayout>
<FrameLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/schedule_grid_frame"
android:minHeight="179dp"
android:layout_toRightOf="#+id/schedule_time_frame"
android:layout_centerHorizontal="false"
android:layout_weight="8">
<include
layout="#layout/schedule_grid_layout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
</FrameLayout>
</LinearLayout>
</ScrollView>
Note that the layout called scheduleGridLayout is where I am trying to draw these rectangles. It has the following XML:
<?xml version="1.0" encoding="utf-8"?>
<(package).ScheduleGridLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:scheduleGridLayout="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
</(package).ScheduleGridLayout>
(When copying the above code, I removed the package name.)
In the constructor for ScheduleGridLayout.java, I call this.setWillNotDraw(false);. Before drawing the rectangles, I draw some horizontal gridlines on ScheduleGridLayout through its overridden onDraw() method:
paint.setStyle(Paint.Style.STROKE);
paint.setColor(Color.LTGRAY);
final int width = getWidth();
final int rowHeight = ScheduleTimeLayout.SCHEDULE_VIEW_ROW_HEIGHT + ScheduleTimeLayout.TIME_LABEL_HEIGHT;
for(int i = 0; i < ScheduleTimeLayout.TIME_SEGMENT_COUNT + 1; i++){
if ((i % (int)(1.0/ScheduleTimeLayout.SEGMENT_SIZE)) == 0){
paint.setColor(Color.GRAY);
paint.setStrokeWidth((float)4.0);
}
else{
paint.setColor(Color.LTGRAY);
paint.setStrokeWidth((float)2.0);
}
int y = 178 + (int)(i*(rowHeight + 1) - rowHeight/2.0);
canvas.drawLine(0, y, width, y, paint);
}
Since I want the rectangles to be clickable, I have to add them as some sort of views rather than simply drawing their images onto the canvas. I've tried a few different ways to draw the rectangles-- currently I try to do it in onFinishInflate() in ScheduleGridLayout.java, like so:
Bitmap bmp = Bitmap.createBitmap(60,200,Bitmap.Config.RGB_565);
Bitmap tempBitmap = Bitmap.createBitmap(bmp.getWidth(), bmp.getHeight(), Bitmap.Config.RGB_565);
Canvas tempCanvas = new Canvas(tempBitmap);
paint = new Paint();
paint.setColor(Color.RED);
tempCanvas.drawBitmap(bmp, 0, 0, null);
tempCanvas.drawRoundRect(new RectF(0,0,600,600), 2, 2, paint);
ImageView testBlock = new ImageView(getContext());
testBlock.setImageDrawable(new BitmapDrawable(getResources(), tempBitmap));
addView(testBlock);
However, there the block does not appear when I do this.
In a separate attempt, I tried creating an XML layout file for the schedule block:
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="wrap_content"
android:layout_width="wrap_content">
<com.dalc.dalccalendar.ScheduleBlock
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:scheduleGridLayout="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/schedule_block">
</com.dalc.dalccalendar.ScheduleBlock>
In ScheduleBlock.java, I override onMeasure() to set the size of the block using setMeasuredDimension, and I override onDraw() with the following code:
super.onDraw(canvas);
paint = new Paint();
paint.setStyle(Paint.Style.FILL);
paint.setColor(Color.RED);
canvas.drawPaint(paint);
String bgColor = "#FF0000";
setBackgroundColor(Color.parseColor(bgColor));
I noticed that red rectangle does appear in the XML file's preview, but not when I try to add the block in ScheduleGridLayout:
FrameLayout blockFrame = (FrameLayout)LayoutInflater.from(getContext()).inflate(R.layout.schedule_block_container, this, false);
testBlock.setElevation(4);
addView(blockFrame);
Not only does the rectangle not appear when I run the app, but I've found that the onDraw() method for ScheduleBlock is not called. What am I doing wrong? How can I make these blocks appear inside my scrollview on top of the gridlines and be interactable with clicks? I'd prefer to be able to do with with custom views if possible, since each block needs to be associated with specific data.