I want there to be an icon, which when clicked on, will be replaced by a spinning progress bar. Once the appropriate task in the background has finished, the ProgressBar should be replaced by the icon again.
This is similar to the progress bar we all are used to in the action bar (e.g. as described here), but I want to achieve the same thing within a Fragment (dialog), so setActionView() is not available.
What would be the best way to tackle this?
So your next stop is ProgressButton by SundeepK (MIT License)
ProgressButton can be used to display a simple rotating Drawable to give the user
the effect of a loading button. The Drawable will be displayed once the user clicks the button and will have to be manually dismissed using the stopLoadingAnimation() method.
ProgressButton class:
public class ProgressButton extends ImageButton {
private boolean _shouldDisplayLoadingAnimation = false;
private Drawable _loadingAnimation;
private TextPaint _textPaint;
private Rect _textBounds;
private String _defaultText;
public ProgressButton(Context context_, AttributeSet attrs_, int defStyle_) {
super(context_, attrs_, defStyle_);
final TypedArray a = context_.obtainStyledAttributes(attrs_, R.styleable.ProgressButton,
R.attr.progressButtonStyle, R.style.ProgressButton_attrs);
this.setBackgroundColor(a.getInteger(R.styleable.ProgressButton_defaultBackgroundColor, Color.WHITE));
_loadingAnimation = getDrawable();
_loadingAnimation.setAlpha(0);
_defaultText = a.getString(R.styleable.ProgressButton_defaultText);
_textPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
_textPaint.density = getResources().getDisplayMetrics().density;
_textPaint.setColor(a.getInteger(R.styleable.ProgressButton_defaultFontColor, Color.BLACK));
_textPaint.setTextAlign(Align.CENTER);
_textPaint.setTextSize(a.getInteger(R.styleable.ProgressButton_defaultFontSize, 40));
_textPaint.setFakeBoldText(true);
_textBounds = new Rect();
a.recycle();
}
public ProgressButton(Context context_, AttributeSet attrs_) {
this(context_, attrs_, 0);
}
public ProgressButton(Context context_) {
this(context_, null);
}
#Override
public boolean performClick() {
boolean isClicked = super.performClick();
if (isClicked) {
_shouldDisplayLoadingAnimation = true;
this.invalidate();
}
return isClicked;
};
public void startLoadingAnimation() {
_shouldDisplayLoadingAnimation = true;
this.invalidate();
}
public void stopLoadingAnimation() {
_shouldDisplayLoadingAnimation = false;
this.invalidate();
}
#Override
protected void onDraw(Canvas canvas_) {
if (_shouldDisplayLoadingAnimation) {
shouldShowAnimation(true);
} else {
_textPaint.getTextBounds(_defaultText, 0, _defaultText.length(), _textBounds);
canvas_.drawText( _defaultText , getWidth()/2, (getHeight()/2)+((_textBounds.bottom-_textBounds.top)/2) , _textPaint);
shouldShowAnimation(false);
_loadingAnimation.setVisible(false, false);
}
super.onDraw(canvas_);
}
private void shouldShowAnimation(boolean shouldShow_) {
if (_loadingAnimation instanceof Animatable) {
if (shouldShow_) {
_loadingAnimation.setAlpha(255);
((Animatable) _loadingAnimation).start();
} else {
_loadingAnimation.setAlpha(0);
((Animatable) _loadingAnimation).stop();
}
}
}
}
define ProgressButton in your layout:
<com.sun.progressbutton.ProgressButton
android:id="#+id/progressView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clickable="true"
android:padding="10dp"
android:src="#drawable/progress_view"
/>
Related
I have two custom views (canvas views) and both of them have an observer which triggers an event on the screen and draw something on canvas. I am reusing the container and rendering the canvas in the same parent layout by removing other views. Right now, no matter what my both view observer values.
I have tried an dirty hack where I am passing a boolan to both views as true being one and other being false on switch which kinda work but I don't want that. Because it is just a hack. Can anyone tell me an android way to do it?
public class FakeViewOne extends View {
private EventViewModel eventViewModel;
private float l = 0.0f;
public FakeViewOne(Context context, View ParentView) {
super(context);
eventViewModel = new ViewModelProvider((ViewModelStoreOwner) getContext()).get(EventViewModel.class);
eventViewModel.getTrigger().observe((LifecycleOwner) getContext(), new Observer<Float>() {
#Override
public void onChanged(Float val) {
l = val;
}
});
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
/*
* if l > 50 change background of a linearlayout
* draw a line on a canvas
* */
invalidate();
}
}
public class FakeViewTwo extends View {
private EventViewModel eventViewModel;
private float l = 0.0f;
public FakeViewTwo(Context context, View ParentView) {
super(context);
eventViewModel = new ViewModelProvider((ViewModelStoreOwner) getContext()).get(EventViewModel.class);
eventViewModel.getTrigger().observe((LifecycleOwner) getContext(), new Observer<Float>() {
#Override
public void onChanged(Float val) {
l = val;
}
});
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
/*
* if l > 50 change background of a tile
*
* */
invalidate();
}
}
CODE Inside a Fragment where I am switch between those two FakeViews. How can I make those not observe values when they are not-rendered/inactive/removed from the view. I am using mycanvas.removeAllViews();.
final RelativeLayout mycanvas = view.findViewById(R.id.myCanvas);
gestureViewModel.getGestureType().observe(getActivity(), new Observer<String>() {
#Override
public void onChanged(#Nullable String s) {
assert s != null;
mycanvas.removeAllViews();
switch (s){
case "shake":
ShakeControl(view,myView,mycanvas);
break;
case "move":
MoveControl(view,myView,mycanvas);
break;
}
}
});
I have solved the problem with removing the observers on window detach in each FakeView.
#Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
Log.d("Detached","Head");
if (eventViewModel != null && eventViewModel.getTrigger().hasObservers()) {
eventViewModel.getTrigger().removeObservers((LifecycleOwner) ctx);
}
}
I wrote a little STT-functionality, with a floating button that is pulsating after being clicked on to notify that the app is listening. This works quite well so far with the one annoying behavior that my floating button does not return to its original size in some cases.
The animation increases and decreases the size of the button, and I guess it gets stuck in the increased state, hence the randomness of this behavior. I just can't figure out how to catch that and set the size to the original one.
Action Listener of my Button:
private View.OnTouchListener setVoiceButtonOnClick()
{
return new View.OnTouchListener()
{
#Override
public boolean onTouch(View v, MotionEvent event)
{
if (event.getAction() == MotionEvent.ACTION_DOWN)
{
if(!voiceButton.isInitialized())
voiceButton.initAnimationValues();
voiceButton.setPressed(true);
listen();
}
return true;
}
};
}
My Button extends FloatingActionButton, and does the following:
public class FloatingVoiceButton extends FloatingActionButton
{
public static final float DEFAULT_ANIMATION_FACTOR = 1.2f;
private boolean isInitialized = false;
private int originalHeight;
private int originalWidth;
private boolean isAnimationRunning;
private ObjectAnimator animator;
public FloatingVoiceButton(Context context)
{
super(context);
}
public void initAnimationValues()
{
isInitialized = true;
isAnimationRunning = false;
originalHeight = getMeasuredHeight();
originalWidth = getMeasuredWidth();
animator = ObjectAnimator.ofPropertyValuesHolder(
this,
PropertyValuesHolder.ofFloat("scaleX", DEFAULT_ANIMATION_FACTOR),
PropertyValuesHolder.ofFloat("scaleY", DEFAULT_ANIMATION_FACTOR));
animator.setDuration(200);
animator.setRepeatCount(ObjectAnimator.INFINITE);
animator.setRepeatMode(ObjectAnimator.REVERSE);
}
public boolean isInitialized()
{
return isInitialized;
}
public void resetButtonSize()
{
setMeasuredDimension(originalWidth, originalHeight);
}
public boolean isAnimationRunning()
{
return isAnimationRunning;
}
public void animate(boolean doAnimation)
{
isAnimationRunning = doAnimation;
if(doAnimation)
animator.start();
else
{
animator.end();
setPressed(false);
resetButtonSize();
//destroyDrawingCache(); tried these without success
//postInvalidate();
}
}
}
Finally I am controlling the button the start and end of the animation with my RecognitionListener:
public class InputVoiceRecognitionListener implements RecognitionListener
{
private EditText targetEditText;
private String originalContent;
private final String DELIMITER = "\n\n";
private FloatingVoiceButton button;
public InputVoiceRecognitionListener(EditText editText, FloatingVoiceButton button)
{
targetEditText = editText;
originalContent = editText.getText().toString();
this.button = button;
}
#Override
public void onReadyForSpeech(Bundle params)
{
button.animate(true);
}
#Override
public void onBeginningOfSpeech()
{
originalContent = targetEditText.getText().toString();
}
#Override
public void onRmsChanged(float rmsdB)
{}
#Override
public void onBufferReceived(byte[] buffer)
{}
#Override
public void onEndOfSpeech()
{
if(button.isAnimationRunning())
button.animate(false);
}
#Override
public void onError(int error)
{
if(button.isAnimationRunning())
button.animate(false);
}
#Override
public void onResults(Bundle results)
{
setRecognizedText(results.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION));
}
#Override
public void onPartialResults(Bundle partialResults)
{
setRecognizedText(partialResults.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION));
}
#Override
public void onEvent(int eventType, Bundle params)
{
}
private void setRecognizedText(ArrayList<String> matches)
{
String result = "";
if(matches != null)
result = matches.get(0);
if((originalContent.trim()).length() > 0)
{
if(!originalContent.endsWith("\n\n"))
result = originalContent + DELIMITER + result;
else result = originalContent + result;
}
targetEditText.setText(result);
targetEditText.setSelection(result.length());
}
}
EDIT
This did it for me:
resettingAnimator = ObjectAnimator.ofPropertyValuesHolder(
this,
PropertyValuesHolder.ofFloat("scaleX", 1.0f),
PropertyValuesHolder.ofFloat("scaleY", 1.0f));
resettingAnimator.setDuration(0);
resettingAnimator.setRepeatCount(1);
and calling resettingAnimator.start(); when I finish my main animation.
Simple solution to this problem is that you define another animation after stopping your repeating one.
I just can't figure out how to catch that and set the size to the original one.
You, that is View, does know what is the "original" size, its the size of the scale factor 1f. So after stopping repeating animation just make another animations to set scale to 1f
PropertyValuesHolder.ofFloat("scaleX", 1f)
PropertyValuesHolder.ofFloat("scaleY", 1f))
This animation will run always, but will not be visible if your button is already at "normal" size.
With this in mind I would recommend that you use some other flag than isAnimationRunning(), either by some state (ex. selected) of your Fab, or some manually set arbitrary boolean.
Now I have xml to set the view
And I have one View and on button.Now With the onDraw() function I just draw the ColorDotView but I can not draw the button.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.example.chg.colordotview2_button.ColorDotView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/dotView"/>
<Button
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:id="#+id/button"/>
</LinearLayout>
And this is my View Setting class
public class ColorDotView extends View {
int count = 0;
public ColorDotView(Context context) {
super(context);
}
// Constructor required for inflation from resource file
public ColorDotView(Context context, AttributeSet ats, int defaultStyle)
{
super(context, ats, defaultStyle );
}
//Constructor required for inflation from resource file
public ColorDotView(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
public boolean onKeyDown(int keyCode, KeyEvent keyEvent) {
return true; // Return true if the event was handled.
}
#Override
public boolean onKeyUp(int keyCode, KeyEvent keyEvent) {
return true; // Return true if the event was handled.
}
#Override
public boolean onTrackballEvent(MotionEvent event ) {
// Get the type of action this event represents
int actionPerformed = event.getAction();
return true; // Return true if the event was handled.
}
#Override
public boolean onTouchEvent(MotionEvent event) {
// Get the type of action this event represents
invalidate();
return true; // Return true if the event was handled.
}
#Override
protected void onDraw(Canvas canvas) {
canvas.drawColor(Color.BLACK); // background color
Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
if (count%8==0|count%8==1){
mPaint.setColor(Color.RED); } // circle color
if (count%8==2|count%8==3) {
mPaint.setColor(Color.BLUE); // circle color
}
if (count%8==4|count%8==5){
mPaint.setColor(Color.GREEN); } // circle color
if (count%8==6|count%8==7) {
mPaint.setColor(Color.YELLOW); // circle color
}
++count;
//canvas.drawCircle(cx, cy,radios,paint);
canvas.drawCircle(canvas.getWidth() / 2, canvas.getHeight() / 2,
canvas.getWidth() / 4, mPaint);
}
}
This is my MainActivity.class
public class MainActivity extends AppCompatActivity {
Button button;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(new ColorDotView(this));
}
}
I want to add one more button into the view and use the button to control the color change (now I can use screen to change the color)
Thanks in advance!
Because there is no one answer this question.
So I will add my method here.
public class MainActivity extends AppCompatActivity {
Button button;
ColorDotView colorDotView;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
BaseView_ColorButton cet = new BaseView_ColorButton(this);
setContentView(cet);
button = (Button) findViewById(R.id.button);
colorDotView = (ColorDotView) findViewById(R.id.dotView);
button.setOnClickListener(new Button.OnClickListener() {
public void onClick(View v) {
colorDotView.invalidate();
}
});
}
}
Here is the MainActivity function.
The most Important is that we should call the invalidate() function to force the Canvas to draw the new picture when we click the Button
I got a fragment inside of an Activity which contains a TextView and a Button.
If I click the Button another Fragment loads and the text of the TextView in the first Fragment is now the title of the Toolbar. I want to animate this change with the Transition Framework and something like changeBounds but I don't know how to set a transition for the Toolbar.
Could someone point me towards a solution?
You can create a custom transition that animates a TextView's text size as follows:
public class TextSizeTransition extends Transition {
private static final String PROPNAME_TEXT_SIZE = "lychmanit:transition:textsize";
private static final String[] TRANSITION_PROPERTIES = { PROPNAME_TEXT_SIZE };
private static final Property<TextView, Float> TEXT_SIZE_PROPERTY =
new Property<TextView, Float>(Float.class, "textSize") {
#Override
public Float get(TextView textView) {
return textView.getTextSize();
}
#Override
public void set(TextView textView, Float textSizePixels) {
textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSizePixels);
}
};
public TextSizeTransition() {
}
public TextSizeTransition(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
public String[] getTransitionProperties() {
return TRANSITION_PROPERTIES;
}
#Override
public void captureStartValues(TransitionValues transitionValues) {
captureValues(transitionValues);
}
#Override
public void captureEndValues(TransitionValues transitionValues) {
captureValues(transitionValues);
}
private void captureValues(TransitionValues transitionValues) {
if (transitionValues.view instanceof TextView) {
TextView textView = (TextView) transitionValues.view;
transitionValues.values.put(PROPNAME_TEXT_SIZE, textView.getTextSize());
}
}
#Override
public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues,
TransitionValues endValues) {
if (startValues == null || endValues == null) {
return null;
}
Float startSize = (Float) startValues.values.get(PROPNAME_TEXT_SIZE);
Float endSize = (Float) endValues.values.get(PROPNAME_TEXT_SIZE);
if (startSize == null || endSize == null ||
startSize.floatValue() == endSize.floatValue()) {
return null;
}
TextView view = (TextView) endValues.view;
view.setTextSize(TypedValue.COMPLEX_UNIT_PX, startSize);
return ObjectAnimator.ofFloat(view, TEXT_SIZE_PROPERTY, startSize, endSize);
}
}
Since changing the TextView's text size will cause its layout bounds to change during the course of the animation, getting the transition to work properly will take a little more effort than simply throwing a ChangeBounds transition into the same TransitionSet. What you will need to do instead is manually measure/layout the view in its end state in a SharedElementCallback.
I have around twenty custom buttons in my app, with a different image for each button.
I know that to create a click effect I have to create an XML resource like this, I have to created 20 different XML resources for my buttons.
Is there a better way to get the same result without creating separate XML resources for each button?
Update:
Can we make the button translucent when it is clicked.
Found the solution Here.
public class SAutoBgButton extends Button {
public SAutoBgButton(Context context) {
super(context);
}
public SAutoBgButton(Context context, AttributeSet attrs) {
super(context, attrs);
}
public SAutoBgButton(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
#Override
public void setBackgroundDrawable(Drawable d) {
// Replace the original background drawable (e.g. image) with a LayerDrawable that
// contains the original drawable.
SAutoBgButtonBackgroundDrawable layer = new SAutoBgButtonBackgroundDrawable(d);
super.setBackgroundDrawable(layer);
}
/**
* The stateful LayerDrawable used by this button.
*/
protected class SAutoBgButtonBackgroundDrawable extends LayerDrawable {
// The color filter to apply when the button is pressed
protected ColorFilter _pressedFilter = new LightingColorFilter(Color.LTGRAY, 1);
// Alpha value when the button is disabled
protected int _disabledAlpha = 100;
public SAutoBgButtonBackgroundDrawable(Drawable d) {
super(new Drawable[] { d });
}
#Override
protected boolean onStateChange(int[] states) {
boolean enabled = false;
boolean pressed = false;
for (int state : states) {
if (state == android.R.attr.state_enabled)
enabled = true;
else if (state == android.R.attr.state_pressed)
pressed = true;
}
mutate();
if (enabled && pressed) {
setColorFilter(_pressedFilter);
} else if (!enabled) {
setColorFilter(null);
setAlpha(_disabledAlpha);
} else {
setColorFilter(null);
}
invalidateSelf();
return super.onStateChange(states);
}
#Override
public boolean isStateful() {
return true;
}
}
}
Use this in xml:
<net.shikii.widgets.SAutoBgButton
android:layout_width="wrap_content" android:layout_height="wrap_content"
android:background="#drawable/button_blue_bg"
android:text="Button with background image" />