Draw complex drawable in api lower than 23 - java

I'm trying to draw drawable consisting of circle and rectangle. It's working fine while api is set to 23. I noticed that following attributes of 'item' have been added in newest api: height, gravity, weight. Is it possible to get the effect in older sdk?
Drawable:
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:height="5dp"
android:gravity="center_vertical"
android:left="5dp">
<shape android:shape="rectangle">
<size
android:width="50dp"
android:height="5dp" />
<solid android:color="#125572" />
</shape>
</item>
<item
android:width="20dp"
android:height="20dp"
android:gravity="center_vertical">
<shape android:shape="oval">
<solid android:color="#125572" />
<size
android:width="20dp"
android:height="20dp" />
</shape>
</item>
</layer-list>
Image using api 23:
Image using api 22:

I know this question is quite old but I solved this problem and maybe it is useful for someone even now.
For the solution I created two classes.
One class called MyLayerDrawable that extends LayerDrawable. The important thing to keep in mind about this class is that you can get the top and the left insets for each layer.
public class MyLayerDrawable extends LayerDrawable{
private List<Layer> layers;
//some code here
public static class Layer{
private Drawable drawable;
private int topInset;
private int leftInset;
public Layer(Drawable drawable,int topInset,int leftInset){
this.leftInset=leftInset;
this.drawable=drawable;
this.topInset=topInset;
}
public Drawable getDrawable(){
return drawable;
}
public int getTopInset(){
return topInset;
}
public int getLeftInset(){return leftInset;}
}
The second class I created is called MyImageViewPatch that owns an ImageView.
The only public method of this class is setImageMyLayerDrawable(MyLayerDrawable layerDrawable). This method delegates setting the image drawable to the ImageView that it owns:
public void setImageMyLayerDrawable(MyLayerDrawable layerDrawable){
imageView.setImageDrawable(layerDrawable);
}
But before calling the setImageDrawable method an observer object has been added to the ImageView in the MyImageViewPatch constructor. This observer is called before drawing the Drawable in the ImageView:
imageView.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
#Override
public boolean onPreDraw() {
MyLayerDrawable layerDrawable=(MyLayerDrawable)imageView.getDrawable();
int numberOfLayers=layerDrawable.getNumberOfLayers();
Drawable drawable;
Rect bounds;
for(int i=0;i<numberOfLayers;i++){//for each layer
drawable=layerDrawable.getDrawable(i);
bounds=drawable.getBounds();
bounds.top=layerDrawable.getLayer(i).getTopInset();
bounds.left=layerDrawable.getLayer(i).getLeftInset();
if(drawable.getIntrinsicHeight()<bounds.height()){
bounds.bottom=drawable.getIntrinsicHeight()+layerDrawable.getLayer(i).getTopInset();
}
if(drawable.getIntrinsicWidth()<bounds.width()){
bounds.right=drawable.getIntrinsicWidth()+layerDrawable.getLayer(i).getLeftInset();
}
}
return true;
}
});
As you can see before drawing the MyLayerDrawable the bounds for each Layer are adjusted based on the left and top insets; this is the key part to get the drawables drawn in the correct size instead of stretched.
Here is the shapes xml file:
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:height="5dp"
android:gravity="center_vertical"
android:left="5dp"
android:top="8dp">
<shape android:shape="rectangle">
<size
android:width="50dp"
android:height="5dp" />
<solid android:color="#125572" />
</shape>
</item>
<item
android:width="20dp"
android:height="20dp">
<shape android:shape="oval">
<solid android:color="#125572" />
<size
android:width="20dp"
android:height="20dp" />
</shape>
</item>
</layer-list>
To centre the rectangle vertically I used android:top="8dp" instead of android:gravity="center_vertical".
Here is the code for the Main Activity:
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ImageView iv=(ImageView)findViewById(R.id.imageView);
MyLayerDrawable myLayerDrawable=MyLayerDrawable.Builder.build(getApplicationContext(),R.drawable.layer_d_example);
MyImageViewPatch myImageViewPatch=new MyImageViewPatch(iv);
myImageViewPatch.setImageMyLayerDrawable(myLayerDrawable);
}
}
I have the complete code in github. You may check it out: https://github.com/marcosbses/my_layout_drawable.git

Related

Setting a background in OnTouch method in android studio

i am creating a piano App in android studio. I am using the onTouch() method to check if the user has pressed a button and then play a sound. I have created two drawable resources for the piano keys, one is to be used when the piano keys are not pressed and another one to be used when a piano key is pressed. How do i set a condition inside my onTouch() method, to set the background to the respective drawable resources when the piano keys are pressed , and when they are not pressed? Thank you!
Here is the code
MainActivity.java
public class MainActivity extends AppCompatActivity implements View.OnTouchListener {
...
#Override
public boolean onTouch(View v, MotionEvent event) {
switch (v.getId()) {
case R.id.p1:
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:{
//set the background to key_pressed
soundPoolObject.play(c3, 1, 1, 0, 0, 1);
break;}
case MotionEvent.ACTION_UP:{
//set the background to key_normal
soundPoolObject.pause(c3);
break;}
}
}
return true;
}
}
Drawable resource key_pressed.xml
<?xml version="1.0" encoding="utf-8"?>
<layer-list
xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape android:shape="rectangle">
<solid android:color="#00000000" />
</shape>
</item>
<item android:top="10dp">
<shape android:shape="rectangle">
<corners android:radius="5dp" />
<solid android:color="#FFFFFF"/>
</shape>
</item>
</layer-list>
Drawable resource key_normal.xml
<?xml version="1.0" encoding="utf-8"?>
<layer-list
xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="#drawable/black_shadow"/>
<item android:bottom="10dp" >
<shape android:shape="rectangle" >
<corners android:radius="5dp" />
<solid android:color="#FFFFFF"/>
</shape>
</item>
</layer-list>
Try this:
...
case R.id.p1:
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:{
//set the background to key_pressed
v.setBackgroundResource(R.drawable.key_pressed);
soundPoolObject.play(c3, 1, 1, 0, 0, 1);
break;}
case MotionEvent.ACTION_UP:{
//set the background to key_normal
v.setBackgroundResource(R.drawable.key_normal);
soundPoolObject.pause(c3);
break;}
}
...

How to set a custom attribute on a view programmatically

I'm trying to set a custom attribute to my view programmatically, to set a validation state.
First, I created this layout for a text field:
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape>
<solid android:color="#android:color/transparent" />
</shape>
</item>
<item android:right="1dp" android:left="1dp">
<shape>
<solid android:color="#color/state_color" />
</shape>
</item>
<!-- main color -->
<item
android:bottom="1.5dp"
android:left="2.5dp"
android:right="2.5dp">
<shape>
<solid android:color="#android:color/white" />
</shape>
</item>
<!-- draw another block to cut-off the left and right bars -->
<item android:bottom="5.0dp">
<shape>
<solid android:color="#android:color/white" />
</shape>
</item>
Then defined this attribute:
<attr name="validation_state" format="enum">
<enum name="None" value="0"/>
<enum name="Error" value="1"/>
<enum name="Warning" value="2"/>
<enum name="Success" value="3"/>
</attr>
I also defined these color definitions state_color.xml:
<selector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:color="#color/state_error"
app:validation_state="Error"/>
<item
android:color="#color/state_warning"
app:validation_state="Warning"/>
<item
android:color="#color/state_success"
app:validation_state="Success"/>
<item
android:color="#color/state_default"/>
And last I created an own EditText class which extends the one of the Android framework
private static final int[] STATE_MANDATORY = { R.attr.mandatory };
private static final int[] STATE_READONLY = { R.attr.readonly };
private static final int[] STATE_VALIDATION_STATE = { R.attr.validation_state };
private boolean _mandatory = false;
private boolean _readonly = false;
private ValidationState _validationState = ValidationState.NONE;
public mcEditText(Context context, AttributeSet attrs)
{
super(context, attrs);
}
#Override
public void setMandatory(final boolean mandatory)
{
_mandatory = mandatory;
refreshDrawableState();
}
#Override
public void setReadOnly(final boolean readonly)
{
_readonly = readonly;
refreshDrawableState();
}
#Override
public void setValidationState(final ValidationState state)
{
_validationState = state;
refreshDrawableState();
}
#Override
protected int[] onCreateDrawableState(int extraSpace)
{
final int[] drawableState = super.onCreateDrawableState(extraSpace + 3);
if (_mandatory)
{
mergeDrawableStates(drawableState, STATE_MANDATORY);
}
if (_readonly)
{
mergeDrawableStates(drawableState, STATE_READONLY);
}
if (_validationState != ValidationState.NONE)
{
mergeDrawableStates(drawableState, STATE_VALIDATION_STATE);
}
return drawableState;
}
I've added this color definition to my view and it also recognises it, so the error state gets shown. But now I don't know how to switch between the different states. I need to set programmatically the attribute "validation_state", but how is that possible?
It seems that android does not support enum attributes for this kind of work. You must have to work with boolean attributes
<attr name="validation_state_none" format="boolean"/>
<attr name="validation_state_error" format="boolean"/>
...
The most common example of what you trying to do is like focusable, editable attributes. And it's handled with atomic booleans attributes in android source
Another solution that might help you (Not tested) is to create your own drawable programmatically. Take a look at DrawableContainer and StateListDrawable

Edit color of underline in Edittext Android

I want to change color of underline in Edittext and i follow some tutorials to do this.
I create this xml file:
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
<item>
<shape android:shape="rectangle" >
<solid android:color="#00FFFFFF" />
<padding android:bottom="2dp" />
<stroke
android:width="1dp"
android:color="#FFFFFF" />
</shape>
</item>
</layer-list>
But i only do the underline not all rectangle, similiar as default line but white inside of blue and solid background transpartet.
How can i do this?
Thanks
You can change it programatically too:
public static void changeEditTextUnderlineColor(EditText editText) {
int color = Color.parse("#[putColorHere]")
Drawable drawable = editText.getBackground();
drawable.setColorFilter(color, PorterDuff.Mode.SRC_ATOP);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
editText.setBackground(drawable);
} else {
editText.setCompoundDrawables(null, null, drawable, null);
}
}
you can change Edit color Underline in EditText specifying it in styles.xml. In your app theme styles.xml add the following.
<item name="android:textColorSecondary">#color/primary_text_color</item>

Stroke of drawable clips when size increases

I have a drawable circle
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<gradient
android:type="radial"
android:startColor="#FFFFFF"
android:endColor="#EEEEEE"
android:gradientRadius="90"
/>
<size android:width="50dp" android:height="50dp"/>
<stroke android:width="2dp" android:color="#999999"/>
</shape>
And I'm trying to change the stroke of this circle on code side, like so:
public void onStrokeNeedsToChange(int newStroke)
{
GradientDrawable gradientDrawable = (GradientDrawable)mButton.getBackground();
gradientDrawable.setStroke(newStroke, Color.RED);
}
However, when I change the size of the stroke it is being clipped as if the whole drawable button is inside of a square.
Does anybody know how to increase the size of the stroke without it clipping?

android button with double border and gradient

I want to create a custom button.
This button should have a gradient and a two pixel border, but the inner and outer edge should be in a different color (example: inner is red and outer is yellow).
My question: how do I program the double border (like in the image)?!
Image:
I tried with an XML file with two strokes, but it doesn't work.
I could do this with a 9png file, but I want to do it with pure coding.
btn_bg.xml
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item >
<shape android:shape="rectangle">
<padding android:left="3.5px"
android:top="3.5px"
android:right="3.5px"
android:bottom="3.5px"/>
<solid android:color="#d4e23a"/>
</shape>
</item>
<item >
<shape android:shape="rectangle">
<padding android:left="4.5px"
android:top="4.5px"
android:right="4.5px"
android:bottom="4.5px"/>
<solid android:color="#d4413a"/>
</shape>
</item>
<item >
<shape android:shape="rectangle">
<gradient android:startColor="#37c325"
android:endColor="#2573c3"
android:angle="-90"/>
</shape>
</item>
</layer-list>
set the above xml as button background.
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text=""
android:background="#drawable/btn_bg"
android:gravity="center"
android:padding="10dp"
android:textStyle="bold"
>
</Button>
Result:
If you want to go by plain Java code then you need to create a class which extends the button, write all your logic in
public void onDraw(Canvas iCanvas).
I have pasted small code snippet from one of my project. Give it a try. thought I have not created the gradient, I have used plain colors.
public class MyButton extends Button {
private Paint m_paint1 = new Paint();
private Paint m_paint2 = new Paint();
private int m_color1 = 0XFF92C84D; // LIKE AN OLIVE GREEN..
private int m_color2 = 0XFFFF0000; // LIKE AN OLIVE GREEN..
private RectF innerRect1, innerRect2;
public MyButton(Context context) {
super(context);
setBackgroundColor(Color.BLACK);
}
public void onDraw(Canvas iCanvas) {
// draw the button background
m_paint1.setColor(m_color1);
m_paint2.setColor(m_color2);
innerRect1 = new RectF(5, 5, getWidth() - 5, getHeight() - 5);
innerRect2 = new RectF(10, 10, getWidth() - 10, getHeight() - 10);
iCanvas.drawRoundRect(innerRect1, 0, 0, m_paint1);
iCanvas.drawRoundRect(innerRect2, 0, 0, m_paint2);
}
public static RelativeLayout.LayoutParams GetRelativeParam(int iLeft,
int iTop, int iWidth, int iHeight) {
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(
iHeight, iWidth);
params.leftMargin = iLeft;
params.topMargin = iTop;
return params;
}
}
and
RelativeLayout relLay = new RelativeLayout(this);
MyButton m_button = new MyButton(this);
setContentView(relLay);
relLay.addView(m_button, MyButton.GetRelativeParam(0, 0, 100, 500));
Put the button inside a layout that you will create just for it. So set to the layout the outest background color that you want.

Categories