Is there any TextViewCompat equivalent for Button class? - java

To support RTL even in older version of Android, I can simply do the following for TextView.
TextViewCompat.setCompoundDrawablesRelativeWithIntrinsicBounds(
textView, 0, 0, R.drawable.baseline_recommend_24, 0
);
But, what about Button? Is there something like ButtonCompat class?
Currently, I am getting warning from compiler, on old API, if I write the code the following way.
// button is type Button.
button.setCompoundDrawablesRelativeWithIntrinsicBounds(
smallLockedIconResourceId, 0, 0, 0
);

I tried to build my own utility class since I cannot find an official one.
public class ButtonCompat {
public static void setCompoundDrawablesRelativeWithIntrinsicBounds(Button button, int start, int top, int end, int bottom) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
button.setCompoundDrawablesRelativeWithIntrinsicBounds(start, top, end, bottom);
} else {
if (isLeftToRight()) {
button.setCompoundDrawablesWithIntrinsicBounds(start, top, end, bottom);
} else {
button.setCompoundDrawablesWithIntrinsicBounds(end, top, start, bottom);
}
}
}
public static Drawable[] getCompoundDrawablesRelative(#NonNull Button button) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
return button.getCompoundDrawablesRelative();
} else {
if (isLeftToRight()) {
return button.getCompoundDrawables();
} else {
// If we're on RTL, we need to invert the horizontal result like above
final Drawable[] compounds = button.getCompoundDrawables();
final Drawable start = compounds[2];
final Drawable end = compounds[0];
compounds[0] = start;
compounds[2] = end;
return compounds;
}
}
}
}
I am using this method to determine left/ right
https://stackoverflow.com/a/26550588/72437
public static boolean isLeftToRight() {
return MyApplication.instance().getResources().getBoolean(R.bool.is_left_to_right);
}

Related

Is there a way to restart marquee/scrolling when the last letter of the text becomes visible?

I want a text inside a textbox to scroll to end and then restart.
Using marquee scrolls the whole text until the first letter reaches the "start point", but I want the text only to scroll until the last letter of the text becomes visible, then pause and the text should jump to start and start scrolling again.
Is there a way to do this. I have search but I can't find something that works for me.
Thanks,
Sebi
I am not aware of any way to configure a TextView to take on the marquee behavior that you are seeking. The Stack Overflow Q/A I referred you to in the comments does translate the entire TextView. I think that this will work as a marquee if you set the TextView within a ViewGroup such that the TextView is clipped. You can also do you own clipping. You would have to try this to see if it would work. If this does work, it may be sufficient for your needs, but you may be missing the fading edge and maybe some other characteristics of true marquee scrolling. Also, some other characteristics of the TextView such as shadows, borders and backgrounds might not look right.
The contents of the TextView is animated by the TextView code itself through canvas translation for marquee scrolling. See here.
if (isMarqueeFadeEnabled()) {
if (!mSingleLine && getLineCount() == 1 && canMarquee()
&& (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) != Gravity.LEFT) {
final int width = mRight - mLeft;
final int padding = getCompoundPaddingLeft() + getCompoundPaddingRight();
final float dx = mLayout.getLineRight(0) - (width - padding);
canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
}
if (mMarquee != null && mMarquee.isRunning()) {
final float dx = -mMarquee.getScroll();
canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
}
}
You can also consider writing a custom TextView and doing your own text scrolling. You can look to the TextView code itself for some tips on how you might do it. I think writing your own TextView would have better results. (IMO)
I have rewrite the Code from the GitHub Project here (which was posted in the comment from Cheticamp) to java and shorten it to my needs. Now it starts the animation infinite.
Here the java code:
MarqueeTextView.java:
public class MarqueeTextView extends androidx.appcompat.widget.AppCompatTextView {
private final Scroller mScroller;
private int mDelay;
private int mDuration;
private final Handler mHandler = new Handler();
private final Runnable mAnimateRunnable = new Runnable() {
#Override
public void run() {
//check if Animation is needed otherwise check next cycle (if text has changed for example)
if (getTextViewWidthWithPadding() < getTextWidth()) {
mScroller.startScroll(getStartX(), 0, 0, 0, 0);
invalidate();
int direction = getLayout().getParagraphDirection(0);
mScroller.startScroll(getStartX(), 0, (getTextWidth() - getTextViewWidthWithPadding()) * direction, 0, mDuration);
}
mHandler.postDelayed(mAnimateRunnable, mDelay);
}
};
public MarqueeTextView(Context context) {
this(context, null);
}
public MarqueeTextView(Context context, AttributeSet attributeSet) {
super(context, attributeSet);
mScroller = new Scroller(context, new LinearInterpolator());
setScroller(mScroller);
}
private int getTextWidth() {
String text = getText().toString();
TextPaint paint = getPaint();
Rect bounds = new Rect();
paint.getTextBounds(text, 0, text.length(), bounds);
return bounds.width();
}
private int getTextViewWidthWithPadding() {
return getWidth() - (getPaddingStart() + getPaddingEnd());
}
private int getStartX() {
boolean isRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
int lineRight = (int) getLayout().getLineRight(0);
if (isRtl) {
return lineRight - getTextViewWidthWithPadding();
} else {
return 0;
}
}
public void startAnimation(int delay, int duration) {
mDelay = delay;
mDuration = duration;
mHandler.postDelayed(mAnimateRunnable, delay);
}
}
activity_main.xml:
<MarqueeTextView
android:id="#+id/name"
android:layout_width="250dp"
android:layout_height="wrap_content"
android:ellipsize="none"
android:singleLine="true"
android:text="1. Simple text that shows how to use custom marquee"
/>
MainActivty.java:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
((MarqueeTextView)findViewById(R.id.name)).startAnimation(3000, 2000);
}

Android CircularReveal bug: Not working after starting another type of animator on the same view

I have an app with a RecyclerView and in each item I implemented Swipe to star/dismiss behavior using onTouch(). There's no problem with my implementation and I can handle right and left swipe and claim drag from RecyclerView.
Each item is RevealFrameLayout that consists of content on the top, and below there is the original layout and the layout to be revealed, just like this:
Top layout:
Below layout (top revealed layer):
Below layout (default layout before reveal):
There I reveal the top revealed layers (colored ones) and everything works, until I added another ObjectAnimator to hide the star layout (make starred/unstarred), when The reveal works for the first time, and then another swipe will execute the hide animator, then 3rd time, where it's supposed to reveal once again, it really starts the animation (by debugging start() is executed) but it doesn't show up. 4th time the hiding animator works but no revealed layout.
The method to trigger any action:
void triggerAction(SwipeAction swipeAction, final Note note, final int position) {
if (swipeAction.getActionId() == SwipeAction.STAR) {
if (note.isStarred() && !hidden && !hideAnimating && !justAnimated) {
starActionLayout.setVisibility(View.VISIBLE);
archiveActionLayout.setVisibility(View.GONE);
Animator hideAnim = AnimationUtils.createHideAnimation(revealStar);
hideAnim.addListener(new AnimatorListenerAdapter() {
#Override
public void onAnimationEnd(Animator animation) {
revealStar.setVisibility(View.INVISIBLE);
hideAnimating = false;
hidden = true;
note.setStarred(false);
MainActivity.notesController.set(position, note);
super.onAnimationEnd(animation);
}
});
hideAnim.start();
revealStar.setVisibility(View.VISIBLE); // DEBUG: Only to make sure the star layout is shown.
justAnimated = true;
hideAnimating = true;
revealAnimating = false;
revealed = false;
hidden = false;
} else if (!note.isStarred() && !revealAnimating && !revealed && !justAnimated) {
starActionLayout.setVisibility(View.VISIBLE);
archiveActionLayout.setVisibility(View.GONE);
final ObjectAnimator revealAnim = (ObjectAnimator) AnimationUtils.createCircularReveal(revealStar, (int) getCenterX(starActionImage), (int) getCenterY(starActionImage), 0, (float) getRadius(starActionLayout));
revealAnim.addListener(new AnimatorListenerAdapter() {
#Override
public void onAnimationEnd(Animator animation) {
revealAnimating = false;
revealed = true;
note.setStarred(true);
MainActivity.notesController.set(position, note);
super.onAnimationEnd(animation);
}
});
revealAnim.start();
justAnimated = true;
revealAnimating = true;
hideAnimating = false;
hidden = false;
revealed = false;
revealStar.setVisibility(View.VISIBLE);
}
} else if (swipeAction.getActionId() == SwipeAction.ARCHIVE) {
if (!revealAnimating && !revealed) {
int added = starActionLayout.getWidth();
starActionLayout.setVisibility(View.GONE);
archiveActionLayout.setVisibility(View.VISIBLE);
Animator revealAnim = AnimationUtils.createCircularReveal(revealArchive, (int) getCenterX(archiveActionImage) + added, (int) getCenterY(archiveActionImage), 0, (float) getRadius(archiveActionLayout));
revealAnim.addListener(new AnimatorListenerAdapter() {
#Override
public void onAnimationEnd(Animator animation) {
revealAnimating = false;
revealed = true;
super.onAnimationEnd(animation);
}
});
revealAnim.start();
revealAnimating = true;
revealArchive.setVisibility(View.VISIBLE);
}
}
}
There's no problem with my conditions as debugging shows that the reveal animator show code is executed when it should. And AnimationUtils is a custom class that wraps ViewAnimationUtils.createCircularReveal(params) and another custom ObjectAnimator createHideAnimator() and there's no problem with that too.
AnimationUtils.java:
package com.skaldebane.util.graphics;
import android.animation.Animator;
import android.animation.ObjectAnimator;
import android.os.Build;
import android.view.View;
import android.view.ViewAnimationUtils;
import android.view.animation.LinearInterpolator;
public class AnimationUtils {
public static Animator createCircularReveal(View view, int centerX, int centerY, float startRadius, float endRadius) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) return ViewAnimationUtils.createCircularReveal(view, centerX, centerY, startRadius, endRadius);
else return io.codetail.animation.ViewAnimationUtils.createCircularReveal(view, centerX, centerY, startRadius, endRadius);
}
public static Animator createHideAnimation(View view) {
ObjectAnimator hideAnim = new ObjectAnimator();
hideAnim.setPropertyName("alpha");
hideAnim.setFloatValues(1.0F, 0.0F);
hideAnim.setDuration(300);
hideAnim.setInterpolator(new LinearInterpolator());
hideAnim.setTarget(view);
return hideAnim;
}
}
Note: Don't tell me to use ItemTouchHelper instead as that is not the subject of my question and it doesn't solve the problem either.
I just figured out the problem. It was simple but hard to notice. When I was applying the hide animator it was setting alpha permanently to 0, which meant the view would never appear again. I only had to use view.setAlpha(1.0F); directly after calling view.setVisiblity(View.INVISIBLE);.

How to change background colour to specific viewholder items in a RecycleView?

I am trying to change background color in specific item(s) in a RecycleView.
Because I need to set text too, I have the following code that works fine:
protected void populateViewHolder(RankingViewHolder viewHolder, final Ranking model, int position)
{
final Context mContext = getActivity().getApplicationContext();
viewHolder.txt_name.setText(model.getUserName());
viewHolder.txt_score.setText(String.valueOf(model.getScore()));
viewHolder.txt_class.setText(model.getUser_class());
Picasso.with(mContext).load(model.getAvatarUrl()).error(R.drawable.ic_people_black_24dp).into(viewHolder.personPhoto);
int totalRanking = adapter.getItemCount();
int realRank = totalRanking - viewHolder.getAdapterPosition();
viewHolder.ranknumber.setText("# "+String.valueOf(realRank));
}
This works as I want and realRanktakes the correct values, and the viewHolder.ranknumber.setText("# "+String.valueOf(realRank));
Sets the right text with no problem.
Now I am trying (as I got a number/text result correct, to make an if statement like this:
if(adapter.getItemCount() -viewHolder.getAdapterPosition() == 0)
{
viewHolder.itemView.setBackgroundColor(Color.GREEN);
}
if(adapter.getItemCount() -viewHolder.getAdapterPosition() == 1)
{
viewHolder.itemView.setBackgroundColor(Color.YELLOW);
}
if(adapter.getItemCount() -viewHolder.getAdapterPosition() == 2)
{
viewHolder.itemView.setBackgroundColor(Color.BLUE);
}
(I tried with String.valueOf(realRank)equality, with realRankequality too)
In all cases I have the same result. The color changes as its should at positions 1,2,3 BUT it changes at positions 7,8,9 and 14,15,16 and 21,22,23 etc.
What am I missing here?
public class RankingViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener
{
private ItemClickListener itemClickListener;
public TextView txt_name, txt_score, txt_class, ranknumber;
public ImageView personPhoto;
public RankingViewHolder(View itemView)
{
super(itemView);
txt_name = itemView.findViewById(R.id.txt_name);
txt_score = itemView.findViewById(R.id.txt_score);
personPhoto = itemView.findViewById(R.id.person_photo);
txt_class = itemView.findViewById(R.id.txt_class);
ranknumber = itemView.findViewById(R.id.ranknumber);
itemView.setOnClickListener(this);
}
public void setItemClickListener(ItemClickListener itemClickListener) {
this.itemClickListener = itemClickListener;
}
#Override
public void onClick(View view) {
itemClickListener.onClick(view , getAdapterPosition(),false);
}
}
The adapter:
adapter = new FirebaseRecyclerAdapter<Ranking, RankingViewHolder>(
Ranking.class,
R.layout.layout_ranking,
RankingViewHolder.class,
rankingTbl.orderByChild("score").limitToFirst(100)
)
This line of code int realRank = totalRanking - viewHolder.getAdapterPosition();gives a number (1,2,3,4,5,6 etc.) Why i cannot use this number to check equality?
Notice
Keeping this code for NOT working solution, just for future reference:
if(position == 0){
viewHolder.itemView.setBackgroundColor(Color.GREEN);
}
else if(position == 1){
viewHolder.itemView.setBackgroundColor(Color.YELLOW);
}
else if(position == 2){
viewHolder.itemView.setBackgroundColor(Color.BLUE);
}
else{
viewHolder.itemView.setBackgroundColor(Color.WHITE);
}
This changes the color BUT not for only 3 first items. As you scroll down, changes the color for every 3 first viewable items like before, meaning 1,2,3, 7,8,9, etc.
Update:
I dont use a custom adapter, i use FirebaseRecyclerAdapter.
Thanks to #Muhammad Haroon comment i checked that has getItemViewType. So now i m trying with it like
position =adapter.getItemViewType( 0);
if(position == 0){
viewHolder.itemView.setBackgroundColor(Color.GREEN);
}
Not working for now, but i think its the correct direction...
Update 2
With position its not possible as RecycleView recycles the views so i have the same result. The working code is
if (linearLayoutManager.findFirstVisibleItemPosition() > 0) {
viewHolder.itemView.setBackgroundResource(R.drawable.blackframe);
}
else{
viewHolder.itemView.setBackgroundResource(R.drawable.goldframe);
}
Works fine except that after scrolling loosing the change of background.
So as we want and need the perfection, any idea for keeping even after scroll?
hi try add this in your Adapater it may solve your problem.
#Override
public int getItemViewType(int position) {
return position;
}
Please give this a try
override in your custom adapter
#Override
public long getItemId(int position) {
return position;
}
and in in your adapter object:
myAdapter.setHasStableIds(true);
In populateViewHolder add these line of code
if(position == 0){
viewHolder.itemView.setBackgroundColor(Color.GREEN);
}
else if(position == 1){
viewHolder.itemView.setBackgroundColor(Color.YELLOW);
}
else if(position == 2){
viewHolder.itemView.setBackgroundColor(Color.BLUE);
}
else{
viewHolder.itemView.setBackgroundColor(Color.WHITE);
}
position is a parameter in populateViewHolder.

Do not click the ImageButton after the animation

I'm developing a puzzle game. The essence of the issue is this. I has buttons (ImageButton) in the ArrayList mButtons collection and each button has listener, I prescribe an animation of the button for the listener. When I click on the button, the animation works well, BUT !!! if I go back to that button, I can't move it and not press it! As though on top of it the textures were superimposed! Other buttons are pressed. I checked different ways:
v.getAnimation().reset();
v.getAnimation().cancel();
v.clearAnimation();
v.setAnimation(null);
v.getAnimation().setAnimationListener(null);
did not help! If there are any tips and you have encountered the same issue, please reply!
Picture for understanding:
The code to make it clearer:
public class TouchButtonAction implements View.OnClickListener {
private GameController mController;
private List<ImageButton> mButtons;
private List<GridLayout.LayoutParams> mParams;
private final Animation myAnimA;
private volatile boolean isAnimAFinish = true;
public TouchButtonAction(Context context, GameController controller,
List<ImageButton> buttons, List<GridLayout.LayoutParams> params) {
this.mParams = params;
this.mButtons = buttons;
this.mController = controller;
this.myAnimA = AnimationUtils.loadAnimation(context, R.anim.bounce_swap);
final ButtonBounceInterpolator mInterpolatorA = new ButtonBounceInterpolator(0.07, 20);
this.myAnimA.setInterpolator(mInterpolatorA);
// this.myAnimA.setAnimationListener(new Animation.AnimationListener() {
// #Override
// public void onAnimationStart(Animation animation) {
// isAnimAFinish = false;
//// animation.setFillAfter(false);
//// animation.reset();
//
// }
//
// #Override
// public void onAnimationEnd(Animation animation) {
//// animation.reset();
// animation.cancel();
// isAnimAFinish = true;
//// synchronized (this){
//
//// }
// }
//
// #Override
// public void onAnimationRepeat(Animation animation) {
//
// }
// });
#Override
public void onClick(final View v) {
int a = mButtons.indexOf((ImageButton) v);
int b = 0;
for (ImageButton button : mButtons) {
if (!button.isEnabled()) {
b = mButtons.indexOf(button);
}
}
if (here check logic) {
SoundPlay.playSoundPool(SoundPlay.TOUCH);
if (isAnimAFinish){
// v.post(new Runnable() {
// #Override
// public void run() {
// synchronized (this){
v.startAnimation(myAnimA);
isAnimAFinish = false;
// }
//
// }
// });
}
if (v.getAnimation() != null){
if (v.getAnimation().hasEnded()){
Log.d("LOGGG", "hasEnded Anim");
v.getAnimation().reset();
v.getAnimation().cancel();
v.clearAnimation();
v.setAnimation(null);
v.getAnimation().setAnimationListener(null);
isAnimAFinish = true;
}
}
Collections.swap(mButtons, a, b);
mController.getGridLayout().removeAllViewsInLayout();
mController.getGridLayout().removeAllViews();
for (int i = 0; i < mButtons.size(); i++) { // i < 12
ImageButton button = mButtons.get(i);
mController.getGridLayout().addView(button, mParams.get(i));
}
mController.getGridLayout().invalidate();
} else {
SoundPlay.playSoundPool(SoundPlay.TOUCH_BANG);
}
mController.checkSolution(mButtons);
}
If you want to hide the button after click, then you can use button.setVisibility(View.GONE) or if you want, that after click user can't to click that button anymore, you can use after click button.setClickable(false). I think I helped you. If you have questions, ask me.
How to remove excess texture? Maybe you can keep two images, one with texture, and another without. Upon clicking first one with texture, hide it and show second one.
It turns out that it was not necessary to use AnimationUtils.loadAnimation (), but quite other classes that allow manipulating objects, such as widgets.
Example animation bouncing button:
AnimatorSet as = new AnimatorSet ();
float y = view.getTranslationY (), distance = 20F;
as.playSequentially (
ObjectAnimator.ofFloat (view, "translationY", y-distance), // animation 1
ObjectAnimator.ofFloat (view, "translationY", y), // animation 2
  ObjectAnimator.ofFloat (view, "translationY", y - (distance / 2)), // animation 3
  ObjectAnimator.ofFloat (view, "translationY", y)); // animation 4
as.setDuration (600);
as.start ();
And the animation of your widget starts to jump. I hope this helped you as much as me.

Check if a ScrollView has reached the top of the layout

Is it possible to check if a ScrollView is scrolled all its way in the top?
I want to check this so I can enable a SwipeRefreshLayout, otherwise keeping it disabled.
With a ListView it could be done like this, but there's no setOnScrollListener for ScrollViews
listView.setOnScrollListener(new OnScrollListener() {
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
}
#Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
boolean enable = false;
if(listView != null && listView.getChildCount() > 0){
// check if the first item of the list is visible
boolean firstItemVisible = listView.getFirstVisiblePosition() == 0;
// check if the top of the first item is visible
boolean topOfFirstItemVisible = listView.getChildAt(0).getTop() == 0;
// enabling or disabling the refresh layout
enable = firstItemVisible && topOfFirstItemVisible;
}
swipeRefreshLayout.setEnabled(enable);
}
});
This link might be helpful to You. It shows, how to set scroll listener for ScrollView. Then, refer to #antonio answer.
For your case it would be:
mScrollView.getViewTreeObserver().addOnScrollChangedListener(new ViewTreeObserver.OnScrollChangedListener() {
#Override
public void onScrollChanged() {
int scrollY = mScrollView.getScrollY(); //for verticalScrollView
if (scrollY == 0)
swipeRefresh.setEnabled(true);
else
swipeRefresh.setEnabled(false);
}
});
You can use the getScrollY() method (from the View class)
In Kotlin:
scrollView.viewTreeObserver.addOnScrollChangedListener {
if (!scrollView.canScrollVertically(1)) {
// Bottom of scroll view.
}
if (!scrollView.canScrollVertically(-1)) {
// Top of scroll view.
}
}

Categories