I created edit text with prefix with the help of this example.
Same way I tried to achieve suffix to edit text. I have successfully added suffix to the right side of my cursor.But it's not display at the very end of the Edit text. when I type on it, the typed text overlay on suffix. I know there are other way of achieving this but I'm trying to pull this off.Please help.
Basically I need to fulfill these functionalities.
Suffix should display at the end of the edit text.
Suffix should not be overlay from the text typed by the user.
here's my code.
public class SuffixEditText extends EditText {
private String mSuffix = "Suffix";
private Rect mSuffixRect = new Rect();
public SuffixEditText(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
getPaint().getTextBounds(mSuffix, 0, mSuffix.length(), mSuffixRect);
mSuffixRect.left += getPaint().measureText(" ");
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawText(mSuffix, super.getCompoundPaddingRight(), getBaseline(), getPaint());
}
#Override
public int getCompoundPaddingRight() {
return super.getCompoundPaddingRight()+ mSuffixRect.width();
}
Edit 1:
I tried Iharob Al Asimi's answer and I think it's good point to start. It's working but having following issues.
suffix not align with original text
Not getting original text color
I really like high quality results, it seems that on this platform it doesn't really matter for some programmers.
For me it's really important, that the look of my custom EditText view with a suffix is as natural as possible so I solved it in a very elegant and simple way.
Instead of doing some unreliable layout hacks or attempting to intercept the onDraw() method of the edit text, it would seem more natural to add a compound drawable to the right of the view and draw the text on it.
It's a simple solution, it doesn't break anything and it looks very "native".
From the example you linked, there is something that really helps a lot and it's the implementation of the onMesure() method, it makes room for the compound drawable, and that's precisely how I got the idea of using a compound drawable.
Here is the code so you can use it in future projects if the same requirement arises
public class SuffixEditText extends EditText {
private TextPaint mTextPaint;
private String mSuffix;
private float mSuffixWidth;
private Drawable mSuffixDrawable;
private void initialize(Context context) {
Resources resources = getResources();
mTextPaint = new TextPaint();
mTextPaint.setTextSize(getTextSize());
// Using the same foreground color could
// be confusing.
mTextPaint.setColor(getCurrentHintTextColor());
mTextPaint.setTextAlign(Paint.Align.RIGHT);
mSuffixDrawable = new Drawable() {
#Override
public void draw(#NonNull Canvas canvas) {
if (mSuffix == null)
return;
canvas.drawText(mSuffix, 0, getPaddingTop(), mTextPaint);
}
#Override
public void setAlpha(int alpha) {
}
#Override
public void setColorFilter(ColorFilter colorFilter) {
}
#Override
public int getOpacity() {
return PixelFormat.OPAQUE;
}
};
}
public SuffixEditText(Context context) {
super(context);
initialize(context);
}
public SuffixEditText(Context context, AttributeSet attrs) {
super(context, attrs);
initialize(context);
}
public SuffixEditText(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initialize(context);
}
public void setSuffix(String suffix) {
mSuffix = suffix;
setCompoundDrawables(null, null, mSuffixDrawable, null);
}
#Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mSuffix != null)
mSuffixWidth = mTextPaint.measureText(" " + mSuffix);
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
#Override
public int getCompoundPaddingRight() {
return super.getCompoundPaddingRight() + (int) Math.ceil(mSuffixWidth);
}
}
In my opinion, android's API is horribly designed and it makes doing something simple a very difficult task, when some other times it's incredibly stupid to do some other thing. I would prefer a consistent API like Qt's where you can do anything you want.
NOTE: There might happen to be some optimizations to this code but I am not a Java programmer, so I ignore the way things work and can't come up with more efficient code.
Also, a better implementation would take care of the other drawables in case they were present. Because setting them to null might override the previously set drawables anyway. And clearly, this view can't have an additional right drawable as it is implemented in this code.
Related
I a using MpAndroidChart library. I need to implement a design where I need to color the area between two limit lines. I have attached an image for reference. I have tried multiple ways but I have failed to achieve it. I am using this library for the first time. Can anyone help me about how this could be achieved.
As you can see the green shade behind the line graph. Which is the limit. I need to get that green shade
Thanks in advance,
Anudeep Reddy.
I don't think that there is a direct way to achieve this, but this workaround should help you:
LimitLine ll = new LimitLine(lowerLimit, "Systolic range");
ll.setLineColor(Color.GREEN);
ll.setLineWidth(upperLimit - lowerLimit);
ll.setTextColor(Color.WHITE);
ll.setTextSize(12f);
chart.getAxisLeft().setDrawLimitLinesBehindData(true);
The important thing here is the method setDrawLimitLinesBehindData(true).
As always, all the information is available in the documentation.
I had the same problem but reached a different workaround without having to subclass the LineChart. Using canvas to draw the rectangle works, but you have to translate your charts coordinates to the canvas coordinates. You cannot use a single limit line as there is a limit to the width of the line. The workaround I used was to simply loop through limit lines to create a rectangle within my range.
float increment = (rangeHigh - rangeLow) / 20;
float metricLine = rangeLow;
for(int i = 0; i < 20; i++) {
LimitLine llRange = new LimitLine(metricLine, "");
llRange.setLineColor(Color.parseColor("#b5eb45"));
llRange.setLineWidth(10f);
leftAxis.addLimitLine(llRange);
metricLine = metricLine + increment;
}
As this is still an issue I throw in my two cents.
I tried the solution of #HouseOfHufflepuff but I got the error message that I use too much limit lines in the plot. It seems to work anyway but I guess the performance is not optimal.
So I implemented a subclass for drawing zones in the background. Maybe it's helpful for someone:
public class TargetZoneCombinedChart extends CombinedChart {
protected Paint mYAxisSafeZonePaint;
private List<TargetZone> mTargetZones;
public TargetZoneCombinedChart(Context context) {
super(context);
}
public TargetZoneCombinedChart(Context context, AttributeSet attrs) {
super(context, attrs);
}
public TargetZoneCombinedChart(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
#Override
protected void init() {
super.init();
mYAxisSafeZonePaint = new Paint();
mYAxisSafeZonePaint.setStyle(Paint.Style.FILL);
// mGridBackgroundPaint.setColor(Color.rgb(240, 240, 240));
mTargetZones = new ArrayList<>();
}
#Override
protected void onDraw(Canvas canvas) {
for (TargetZone targetZone : mTargetZones) {
// prepare coordinates
float[] pts = new float[4];
pts[1] = targetZone.lowerLimit;
pts[3] = targetZone.upperLimit;
mLeftAxisTransformer.pointValuesToPixel(pts);
// draw
mYAxisSafeZonePaint.setColor(targetZone.color);
canvas.drawRect(mViewPortHandler.contentLeft(), pts[1], mViewPortHandler.contentRight(),
pts[3], mYAxisSafeZonePaint);
}
super.onDraw(canvas);
}
public void addTargetZone(TargetZone targetZone){
mTargetZones.add(targetZone);
}
public List<TargetZone> getTargetZones(){
return mTargetZones;
}
public void clearTargetZones(){
mTargetZones = new ArrayList<>();
}
public static class TargetZone {
public final int color;
public final float lowerLimit;
public final float upperLimit;
public TargetZone(int color, float lowerLimit, float upperLimit) {
this.color = color;
this.lowerLimit = lowerLimit;
this.upperLimit = upperLimit;
}
}
}
To add a zone you just need to add a target zone object:
float rangeHigh = 180f;
float rangeLow = 80f;
chart.addTargetZone(new TargetZoneCombinedChart.TargetZone( Color.parseColor("#33b5eb45"),rangeLow,rangeHigh));
whereby the ranges are y values of the left axis.
This can be done by sub-classing the chart class (e.g. LineChart) and then overriding the onDraw() method. In the overridden onDraw() you can draw the rectangle(s) you need directly onto the canvas and then call super.onDraw() to complete the rendering of the chart.
There is an example of how to do this on the MP Android Github (see below). I followed the code in the example and it worked well for me.
https://github.com/PhilJay/MPAndroidChart/issues/485
I'm trying to create a custom TextView. I look here for my guidance as well and I made it working, though I'm still confused by why the other component is not being drawn, this is just more simple than the reference guide of android but it's not working in mine.
From the custom class I made (CustomTextView w/c extends TextView),
I try to add a OnTouchListener() and invoke the invalidate() method for both this and other component w/c is a TextView but it still not being drawn.
CustomTextView.java
public class CustomTextView extends TextView {
TextView extra_detail;
public CustomTextView(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CustomTextView);
extra_detail = new TextView(context);
extra_detail.setText("Hey!");
extra_detail.setPadding(20, 0, 0, 0);
extra_detail.setTextSize(2);
a.recycle();
//I also tried this in case the space isn't enough
setMinHeight(80);
extra_detail.invalidate();
this.invalidate();
}
final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//Here's my problem
//both are not being drawn
//even if I remove the other one and vice-versa
canvas.drawText("Hey", 10, 0, paint);
extra_detail.draw(canvas); //is this the right way to do this?
}
#Override
public boolean onTouchEvent(MotionEvent event) {
invalidate();
extra_detail.invalidate();
//still not being drawn
//after the invalidation
Toast.makeText(getContext(), "Hey!", Toast.LENGTH_SHORT).show();
return super.onTouchEvent(event);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
extra_detail.measure(widthMeasureSpec, heightMeasureSpec);
}
}
It seems I'm missing something here but cannot figure it out.
Update
I happen to extend my Activity with ActionBarActivity. Now with this kind of pre defined Activity, there is a title bar implemented and its hiding all the canvas.draw*() I made on the top-left-most of the screen. I figure it out after I invoke the setY(10) then the canvas.drawText() appears.
But I still have the problem of drawing another View in onDraw() event.
How to set button height less then 30px? I tried the next:
setHeight(30); (set 150 is working, but set 30 is not)
setPadding(0,-20,0,-20); (width is changes, height is not)
LinearLayout.LayoutParams (set 150 is working, but set 30 is not)
new Button(this, null, android.R.attr.buttonStyleSmall); - effects only on button text
Button mainButton = new Button(this);
mainButton.setPadding(0, 0, 0, 0);
mainButton.setLayoutParams(new LayoutParams(FlowLayout.LayoutParams.WRAP_CONTENT,FlowLayout.LayoutParams.WRAP_CONTENT));
l.addView(mainButton);
SmallButton mainButton = new SmallButton(this);
mainButton.setText(s);
mainButton.setBackgroundResource(R.drawable.button_drawable);
mainButton.setLayoutParams(new LayoutParams(FlowLayout.LayoutParams.WRAP_CONTENT,FlowLayout.LayoutParams.WRAP_CONTENT));
l.addView(mainButton);
public class SmallButton extends Button {
public SmallButton(Context context) {
super(context);
}
public SmallButton(Context context, AttributeSet attrs) {
super(context, attrs);
}
public SmallButton(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec,MeasureSpec.makeMeasureSpec(MeasureSpec.EXACTLY, 35));
}
#Override
protected void onDraw(Canvas canvas) {
canvas.save();
canvas.translate(0,-5);
super.onDraw(canvas);
canvas.restore();
}
}
I have a Shape drawable with gradient for my background as I don't know the exact width. I also tried to setup its height. Is it possible to make button wrap it text only?
EDIT:
The problem is solved with the use of TextView instead of the Button. Ofcourse if you are not using the specific/overriden things in Button.class.
There are two parameters.
Button.setMinimumHeight() and Button.setMinHeight(). Ensure that both of them are set to value "0".
Every View in Android can have a minimum width and height set for them. A Button will set these depending on the style it is using on the device it is using.
However, this can be overridden in the xml for the button:
android:minHeight="0dip"
android:minWidth="0dip"
As #Sudar Nimalan points out, this is related to the background of the button. If you don't wish to create your own button class, you can simply set the background to something smaller. Try setting the background to a solid colour, or create your own background for it.
try this
mainButton.setIncludeFontPadding(false);
I think, This causes due the default background drawable minimum height size, you can sent a empty background or color and test these scenarios.
If you want to change the height with default drawable, you can create your Custom Button extending the Button class and override on onMeasure and onDraw etc.
override the onDraw to shift the default button drawing:
onDraw(canvas){
canvas.save();
canvas.translate(0,value to shift);
super.onDraw(canves);
canvas.restore();
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec,MeasureSpec.makeMeasureSpec(MeasureSpec.EXACTLY, 35));
}
I'm looking to replicate the following within my application:
As you can see, its basically a button which increases/decreases the value of the text view contained within it. This button will have three visual states -> unpressed, decrease and increase (as seen in the image above, the user taps the increase arrows and the button appears pressed in on that side)
Here are my 3 button states currently:
As you can see, the problem I have is being able to correctly skew/rotate the text view so it looks visually correct and appears slanted along with the button when its being increased or decreased.
I have tried two different approaches so far:
Create a custom text view class which overrides the onDraw() method to skew the canvas:
#Override
public void onDraw(Canvas canvas) {
canvas.save();
canvas.skew(0.2f, 0f);
super.onDraw(canvas);
canvas.restore();
}
Integrate the Rotate3dAnimation class (source here) and used many different variations to get the desired result such as:
Rotate3dAnimation skew = new Rotate3dAnimation(
30, 0, centerX, centerY, 0, false);
txtAmount.startAnimation(skew);
Unfortunately, I'm not quite getting the exact result that mirrors the first image above. I'm getting confused with setting values with the Z-axis, skew, rotate etc.
I'd greatly appreciate any help from anyone who has experience with this stuff. Thanks in advance
Well I even tried and I came up with something like this:
public class DemoActivity extends TextView {
Context context;
String firstText = "$120.00";
public DemoActivity(Context context)
{
super(context);
this.context = context;
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
setText(firstText);
setTextSize(30);
canvas.skew(1.0f, 0.3f); //you need to change values over here
Rotate3dAnimation skew = new Rotate3dAnimation(
-20, 30,200, 200, 0, false); //here too
startAnimation(skew);
}
}
I got an output as:
I guess changing the values by trial and error can solve your problem.
Hope it helps.
Thanks to Parth Doshi answer. His answer need a little tweaking to run which I'm sharing here to save someone else time.
First create a class in src folder and write all of three constructors.
public class TextViewDemo extends TextView {
Context context;
String text = "TESTING 3DX TOOLS";
public TextViewDemo(Context context) {
super(context);
this.context = context;
}
public TextViewDemo(Context context, AttributeSet attrs) {
super(context, attrs);
this.context = context;
}
public TextViewDemo(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
this.context = context;
}
#Override
protected void onDraw(Canvas canvas) {
// TODO Auto-generated method stub
super.onDraw(canvas);
setText(text);
setTextSize(30);
canvas.skew(0.5f, 1.0f); // you need to change values over here
Rotate3dAnimation skew = new Rotate3dAnimation(-50, 30, 0, 0, 0,
false); // here too
startAnimation(skew);
}
}
In you res/layout/my_layout.xml file you can add a tag of your custom made TextView.
<com.yourpackage.name.TextViewDemo
android:id="#+id/name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:text="Hello World"
<!-- All parameters and value shall remain same -->
/>
Like any other view, you can create an instance of TextViewDemo in your onCreate() method
TextViewDemo txtDemo = (TextViewDemo) findViewById(R.id.name);
Regards
public class SynchronisedScrollView extends ListView {
private ScrollViewListener scrollViewListener = null;
public SynchronisedScrollView(Context context) {
super(context);
}
public SynchronisedScrollView(Context context, AttributeSet attrs,
int defStyle) {
super(context, attrs, defStyle);
}
public SynchronisedScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
protected void onScrollChanged(int x, int y, int oldx, int oldy) {
Log.d("Hello", "I am scrolled");
// super.onScrollChanged(x, y, oldx, oldy);
}
}
I want to catch how much the listView is scrolled, so I extend ListView. I am able to scroll the list. But OnScrollChanged is not called. For ScrollView and HorizontalScrollView OnScrollChanged is called whenever it scrolls. Which function is triggered when we scroll a ListView.
In ListView you must explicitly register an onScrollListener to receive onScroll() events.
I think that the method you are looking for is:
public void offsetChildrenTopAndBottom(int offset) {
// ...
}
from ViewGroup. What you can do is extend ListView to gather the offset given to one of the ListViews and pass it though to the other ListView. Make sure that you call super.offsetChildrenTopAndBottom on the recieven end or you could easily end up with a stackoverflow.
I would create a private method and have the new Observable views.
public class ObservableListView extends ListView {
private ObservableListView peer;
// [Constructors] make sure you override all the super constructors.
// Can have trouble with layouts otherwise
public void setPeer(ObservableListView peer) {
this.peer = peer;
}
#Override
public void offsetTopAndBottom(int offset) {
super.offsetTopAndBottom(offset);
if (peer != null) {
peer.internalVerticalOffset(offset);
}
}
private void internalVerticalOffset(int offset) {
super.offsetTopAndBottom(offset);
}
}
If you feel brave you can also define an xml property to give one of the ListViews id and let that one set up the peer relation. (I can expand if you are interested)
My bad, this is non working code. I'm trying to figure out how to do this with the hidden method. In this code I'm overriding the wrong method. Will update shortly.
Update:
Ok, after some research I think that the best option is to combine the extension of ListView with a TouchDelegate.