I'm making a custom View that displays a spinner to show that the camera is focusing.
public class ApertureAnimation extends View
{
private final static String TAG = "ApertureAnimation";
private AnimationDrawable animationDrawable;
public ApertureAnimation(Context context)
{
super(context);
init(null, 0);
}
public ApertureAnimation(Context context, AttributeSet attrs)
{
super(context, attrs);
init(attrs, 0);
}
public ApertureAnimation(Context context, AttributeSet attrs, int defStyle)
{
super(context, attrs, defStyle);
init(attrs, defStyle);
}
private void init(AttributeSet attrs, int defStyle)
{
animationDrawable = (AnimationDrawable) getResources().getDrawable(R.drawable.apperature_focus, null);
}
#Override
protected void onDraw(Canvas canvas)
{
super.onDraw(canvas);
int paddingLeft = getPaddingLeft();
int paddingTop = getPaddingTop();
int paddingRight = getPaddingRight();
int paddingBottom = getPaddingBottom();
int contentWidth = getWidth() - paddingLeft - paddingRight;
int contentHeight = getHeight() - paddingTop - paddingBottom;
if(animationDrawable != null)
{
animationDrawable.setBounds(paddingLeft, paddingTop, paddingLeft + contentWidth, paddingTop + contentHeight);
animationDrawable.start();
animationDrawable.draw(canvas);
}
}
public void start()
{
if(animationDrawable != null)
animationDrawable.start();
}
public void stop()
{
if(animationDrawable != null)
animationDrawable.stop();
}
}
apperature_focus.xml
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
android:oneshot="false">
<item android:drawable="#drawable/apperature_0" android:duration="200" />
<item android:drawable="#drawable/apperature_30" android:duration="200" />
<item android:drawable="#drawable/apperature_60" android:duration="200" />
</animation-list>
The issue I'm having is that only the first frame shows when I call start(). I am able to get the animation to work if I use an ImageView outside of a class extending View, like this https://developer.android.com/guide/topics/graphics/drawable-animation, but when I try to bring that code into this custom UI component it doesn't work.
Related
I am trying to create a seekbar. It has to look like below in picture.
Here is my seekbar in layout:
<SeekBar
android:id="#+id/progress_bar"
android:layout_width="match_parent"
android:layout_height="#dimen/progress_bar_height"
android:minHeight="#dimen/progress_bar_height"
android:maxHeight="#dimen/progress_bar_height"
android:progressDrawable="#drawable/progressbar_seek_bar"
android:thumbOffset="0dp"
android:thumb="#drawable/progressbar_thumb"
android:max="100"/>
Here is progressbar_thumb drawable XML:
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:gravity="center_horizontal">
<bitmap android:src="#drawable/img_grabber_body"/>
</item>
<item>
<bitmap android:src="#drawable/img_grabber_knob_long"/>
</item>
</layer-list>
There are two different images in thumb one vertical line and round knob. My problem is how to use such thumbs in Android seekbar. If I set the thumb in XML it considers width of thumb as width of round knob. Because of this it leaves some space between thumb and progress as you see in below images. I can customize view to show complete thumb but how to remove this extra space around the thumb?
I was facing a similar problem.
I found really useful this code.
You just have to change where the text is drawn in the onDraw(Canvas canvas)
Hope this could help.
EDIT 1:
Here is my code that put the label with the progression percentage under the seekbar thumb. You just have to change values of label_x and label_y in the onDraw(Canvas canvas) to change the position where the label is drawn.
public class SeekBarWithHint extends android.support.v7.widget.AppCompatSeekBar {
private final int MIN_PROGRESS_VALUE = 0;
private final int MAX_PROGRESS_VALUE = 100;
private Paint mSeekBarHintPaint;
private int mHintTextColor;
private float mHintTextSize;
public SeekBarWithHint(Context context) {
super(context);
init();
}
public SeekBarWithHint(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.getTheme().obtainStyledAttributes(
attrs,
R.styleable.SeekBarWithHint,
0, 0);
try {
mHintTextColor = a.getColor(R.styleable.SeekBarWithHint_hint_text_color, 0);
mHintTextSize = a.getDimension(R.styleable.SeekBarWithHint_hint_text_size, 0);
} finally {
a.recycle();
}
init();
}
public SeekBarWithHint(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
setMax(MAX_PROGRESS_VALUE);
mSeekBarHintPaint = new TextPaint();
mSeekBarHintPaint.setColor(mHintTextColor);
mSeekBarHintPaint.setTextSize(mHintTextSize);
}
#Override
protected synchronized void onDraw(Canvas canvas) {
super.onDraw(canvas);
int label_x = (int) (getThumb().getBounds().centerX());
int label_y = getHeight();
canvas.drawText(String.valueOf(getProgress()) + "%", getProgress() > 95 ? label_x - 55 : label_x, label_y, mSeekBarHintPaint);
}
private void animateProgression(int progress) {
final ObjectAnimator animation = ObjectAnimator.ofInt(this, "progress", MIN_PROGRESS_VALUE, progress);
animation.setDuration(3000);
animation.setInterpolator(new DecelerateInterpolator());
animation.start();
this.setProgress(progress);
this.clearAnimation();
}
public void setSeekbarProgress(int progress) {
animateProgression(progress);
}
}
EDIT 2:
Better use this DescreteSeekBar that do EXACTLY what you are looking for.
I am trying to create a textview that will always be squared so I have implemented this custom class.
public class SquareTextView extends TextView {
public SquareTextView(Context context) {
super(context);
}
public SquareTextView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public SquareTextView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int max = Math.max(getMeasuredWidth(), getMeasuredHeight());
setMeasuredDimension(max, max);
}
}
Here is an example layout that illustrates the problem:
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingBottom="8dp"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:paddingTop="8dp">
<com.mypackage.SquareTextView
android:layout_width="wrap_content"
android:layout_height="50dp"
android:layout_gravity="right|top"
android:background="#000"
android:gravity="center"
android:padding="4dp"
android:text="1"
android:textColor="#FFF"/>
</LinearLayout>
Here is an image of this
This works great in getting the view squared, however, it seems like the gravity gets messed up. With this, the text seems to always be in the top left corner. How can I have a TextView that will always be squared but still keep the gravity or at least be able to center the text?
EDIT: After some testing I have noticed that if you set the width or height to a specific dp size the gravity seems to be working again. So it probably has to do with the WRAP_CONTENT attribute. Will that be handled in another way in the onmeasure method that could cause my own method to not work as expected?
Hope you have already got the answer by now. If not, you can use this:
public class TextAlphaSquareTextView extends AppCompatTextView {
private int mTextAlpha = 0;
private boolean isSquare = false;
public TextAlphaSquareTextView(Context context) {
super(context);
init(null);
}
public TextAlphaSquareTextView(Context context, AttributeSet attrs) {
super(context, attrs);
init(attrs);
}
public TextAlphaSquareTextView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(attrs);
}
private void init(AttributeSet attrs) {
if (attrs == null) {
} else {
TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.TextAlphaSquareTextView);
mTextAlpha = a.getInteger(R.styleable.TextAlphaSquareTextView_textAlpha, 100);
isSquare = a.getBoolean(R.styleable.TextAlphaSquareTextView_squareMode, false);
a.recycle();
if(mTextAlpha < 0 || mTextAlpha > 100)
throw new IllegalArgumentException("Alpha range should be b/w 0 to 100 (in percentage)");
else {
setAlphaOnTextColor();
}
}
setText(getText());
}
void setAlphaOnTextColor() {
int alpha = ((255 * mTextAlpha) / 100);
setTextColor(getTextColors().withAlpha(alpha));
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (isSquare) {
int width = this.getMeasuredWidth();
int height = this.getMeasuredHeight();
int size = Math.max(width, height);
int widthSpec = MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY);
int heightSpec = MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY);
super.onMeasure(widthSpec, heightSpec);
}
}
}
you need to call super.onMeasure() again with EXACT spec and the calculated size, since setMeasureDimension() seems to be ignoring the gravity.
I have a custom CircleButton class:
public class CircleButton extends ImageView {
private int radius;
private int x;
private int y;
public CircleButton(Context context) {
super(context);
constructorTask();
}
public CircleButton(Context context, AttributeSet attrs) {
super(context, attrs);
constructorTask();
}
public CircleButton(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
constructorTask();
}
public CircleButton(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
constructorTask();
}
private void constructorTask() {
x = 300;
y = 300;
radius = 100;
}
#Override
public void setPressed(boolean pressed) {
super.setPressed(pressed);
Log.i("Button Logger","Button Pressed");
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawCircle(x, y, radius, GameView.green);
Log.i("Drawing status", "CircleButton Drawing...");
}
}
I have a single activity. This activity contains a relative layout with a single custom view.
Here is the custom view:
public class GameView extends View {
public static Paint green = new Paint();
public GameView(Context context) {
super(context);
green.setARGB(255,0,255,0);
}
public GameView(Context context, AttributeSet attrs) {
super(context, attrs);
green.setARGB(255, 0, 255, 0);
}
public GameView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
green.setARGB(255, 0, 255, 0);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Log.i("GameView Draw Status","Drawing...");
Main.testButton.invalidate();
invalidate();
}
}
And here is the activity code:
public class Main extends AppCompatActivity {
public static CircleButton testButton;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
testButton = new CircleButton(getApplicationContext());
makeFullScreen();
RelativeLayout screenLayout = (RelativeLayout) findViewById(R.id.screenLayout);
screenLayout.addView(testButton);
}
private void makeFullScreen() {...}
}
For some reason my testButton is not being drawn. Why is it not being drawn?
EDIT ONE: Here is the XML I have.
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="0dp"
android:paddingLeft="0dp"
android:paddingRight="0dp"
android:paddingTop="0dp"
tools:context="com.example.vroy.customcirclebuttontest.Main"
android:id="#+id/screenLayout">
<com.example.vroy.customcirclebuttontest.GameView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/black"
android:id="#+id/gameScreen" />
</RelativeLayout>
EDIT TWO: I did some further debugging by adding a normal button to the relative layout and it worked fine.
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
testCircleButton = new CircleButton(getApplicationContext());
makeFullScreen();
testButton = new Button(getApplicationContext());
testButton.setX(100);
testButton.setY(100);
testButton.setText("HELLO WORLD");
RelativeLayout screenLayout = (RelativeLayout) findViewById(R.id.screenLayout);
screenLayout.addView(testCircleButton);
screenLayout.addView(testButton);
Log.i("Button Status","Adding Button To Layout");
}
For some reason by circleButton is not working but a normal button is.
You are not specifying the size of the View (layout_width and layout_height), and thus your view is getting rendered inside a 0px by 0px space and thus invisible.
You can set those programatically using LayoutParams before adding your views to the layout.
For example with absolute size:
testButton.setLayoutParams(new ViewGroup.LayoutParams(100,100));
Although keep in mind the difference between px and dip. You would probably want to set the values using your internal radius attribute instead of harcoding them.
To improve TextView rendering in Instagram, the engineers in Instagram provide a hack in here,they use a custom view(TextLayoutView) to cache the text.Layout, but in this post, they don't give us a demo or tell us how to use it, so if I want to use this hack, how could I do?
This is my simple implementation:
TextLayoutView:
public class TextLayoutView extends View {
private Layout mLayout;
public TextLayoutView(Context context) {
this(context, null);
}
public TextLayoutView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public TextLayoutView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
setFocusable(true);
}
#Override
protected void onDraw(Canvas canvas) {
long t1=System.currentTimeMillis();
super.onDraw(canvas);
canvas.save();
if (mLayout != null) {
canvas.translate(getPaddingLeft(), getPaddingTop());
mLayout.draw(canvas);
}
canvas.restore();
long t2=System.currentTimeMillis();
Log.i("TEST", "onDraw::"+(t2-t1));
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
long t1=System.currentTimeMillis();
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (mLayout != null) {
setMeasuredDimension(
getPaddingLeft() + getPaddingRight() + mLayout.getWidth(),
getPaddingTop() + getPaddingBottom() + mLayout.getHeight());
}
long t2=System.currentTimeMillis();
Log.i("TEST", "onMeasure::"+(t2-t1));
}
public void setTextLayout(Layout layout) {
mLayout = layout;
requestLayout();
}}
you can use it just this:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
......
viewHolder.textView.setTextLayout(getLayout(mList.get(position)));
return convertView;
}
private final Map<String, Layout> mLayoutMap = new ConcurrentHashMap<String, Layout>();
private Layout getLayout(String str) {
Layout layout = mLayoutMap.get(str);
if (layout == null) {
TextPaint textPaint = new TextPaint();
textPaint.setTextSize(20);
layout = new StaticLayout(str, textPaint, width, Alignment.ALIGN_CENTER,
1.0f, 0.0f, true);
mLayoutMap.put(str, layout);
}
return layout;
}
I'm rotating an ImageView by using an Animation defined by a XML file.
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:shareInterpolator="false" >
<rotate
android:interpolator="#android:anim/linear_interpolator"
android:pivotX="50%"
android:pivotY="50%"
android:fromDegrees="0"
android:toDegrees="90"
android:fillAfter="true" />
</set>
Although ImageView has the getRotation() method , it only returns the first value that has been set to the image object.
Is there anyway to get the current rotation degrees by using a XML animation? If not, what should be the best way to go with it?
public class CustomImageView extends ImageView {
private ObjectAnimator rotationAnimator;
public CustomImageView(Context context) {
super(context);
}
public CustomImageView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CustomImageView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
#Override
public void setRotation(float rotation) {
super.setRotation(rotation);
// Do something
}
public synchronized void startAnimation(int animationDuration) {
if (rotationAnimator == null || !rotationAnimator.isRunning()) {
Keyframe kf0 = Keyframe.ofFloat(0f, 0f);
Keyframe kf2 = Keyframe.ofFloat(0.5f, 180f);
Keyframe kf1 = Keyframe.ofFloat(1f, 360f);
PropertyValuesHolder pvhRotation = PropertyValuesHolder.ofKeyframe("rotation", kf0, kf1, kf2);
rotationAnimator = ObjectAnimator.ofPropertyValuesHolder(this, pvhRotation);
rotationAnimator.setRepeatCount(ObjectAnimator.INFINITE);
rotationAnimator.setInterpolator(new LinearInterpolator());
rotationAnimator.setDuration(animationDuration);
rotationAnimator.start();
}
else {
// Already running
}
}
Version 2, since you are having problems
public class CustomImageView extends ImageView {
private ObjectAnimator rotationAnimator;
private float myRotation;
public CustomImageView(Context context) {
super(context);
}
public CustomImageView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CustomImageView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public void setMyRotation(float rotation) {
this.myRotation = rotation;
Log.d("CustomImage", "Rotation: " + rotation);
invalidate();
}
public synchronized void startAnimation(int animationDuration) {
if (rotationAnimator == null || !rotationAnimator.isRunning()) {
Keyframe kf0 = Keyframe.ofFloat(0f, 0f);
Keyframe kf2 = Keyframe.ofFloat(0.5f, 180f);
Keyframe kf1 = Keyframe.ofFloat(1f, 360f);
PropertyValuesHolder pvhRotation = PropertyValuesHolder.ofKeyframe("myRotation", kf0, kf1, kf2);
rotationAnimator = ObjectAnimator.ofPropertyValuesHolder(this, pvhRotation);
rotationAnimator.setRepeatCount(ObjectAnimator.INFINITE);
rotationAnimator.setInterpolator(new LinearInterpolator());
rotationAnimator.setDuration(animationDuration);
rotationAnimator.start();
}
else {
// Already running
}
}
public synchronized void stopAnimation() {
if (rotationAnimator != null) {
rotationAnimator.cancel();
rotationAnimator = null;
}
}
public synchronized boolean getAnimationRunning() {
return rotationAnimator != null && rotationAnimator.isRunning();
}
#Override
protected void onDraw(Canvas canvas) {
canvas.rotate(myRotation, getWidth() / 2.0f, getHeight() / 2.0f);
super.onDraw(canvas);
}
}
Sample logcat output:
04-22 22:48:55.475 16341-16341/animation.example.com.animationdemo D/CustomImage﹕ Rotation: 358.44
04-22 22:48:55.490 16341-16341/animation.example.com.animationdemo D/CustomImage﹕ Rotation: 0.48000813
04-22 22:48:55.505 16341-16341/animation.example.com.animationdemo D/CustomImage﹕ Rotation: 2.4
04-22 22:48:55.525 16341-16341/animation.example.com.animationdemo D/CustomImage﹕ Rotation: 4.44
I have packed the code into a simple project: LINK