Multiple Gradient for single Textview - java

Refering agradient to textview regard as normal action can be achieved in android ,
as below :
android:background="#drawable/gredient"
and gradient XML will be as below code :
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<gradient android:startColor="#B22222" android:centerColor="#000000"
android:endColor="#B22222" android:angle="0" />
<corners android:radius="15dp" />
</shape>
what im asking about is there is away in android to apply multiple gradients to single textview consisting of may paragraphs , so each paragraph will has different gradient .
any advice will be appreciated ,
thanks

You might want to consider using a WebView instead of TextView (if you need more advanced formatting), but if you want to stick to a TextView, LineBackgroundSpan is your friend :)
Although there might be some better solution (most likely involving a custom Layout-derived class), here's some sample code that should do what you want just by implementing a special LineBackgroundSpan interface:
public class MyActivity extends Activity {
private Editable mEditable;
private class MySpan implements LineBackgroundSpan {
private final Rect rect = new Rect();
private final Drawable drawable;
private int next = 0;
public MySpan(Drawable drawable) {
this.drawable = drawable;
}
#Override
public void drawBackground(Canvas c, Paint p, int left, int right, int top, int baseline,
int bottom, CharSequence text, int start, int end, int lnum) {
if (start == end) return;
if (next == 0) {
next = mEditable.nextSpanTransition(start, Integer.MAX_VALUE, MySpan.class);
rect.left = left;
rect.top = top;
}
if (next == end) {
rect.right = right;
rect.bottom = bottom;
c.save();
drawable.setBounds(rect);
drawable.draw(c);
c.restore();
}
}
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final Resources res = getResources();
final Drawable gd1 = res.getDrawable(R.drawable.gd1);
final Drawable gd2 = res.getDrawable(R.drawable.gd2);
final Drawable gd3 = res.getDrawable(R.drawable.gd3);
final Drawable gd4 = res.getDrawable(R.drawable.gd4);
final TextView tv = new TextView(this);
setContentView(tv);
tv.setText("Paragraphs with drawable background:\n", BufferType.EDITABLE);
mEditable = tv.getEditableText();
final String text = "paragraph text ";
appendPara("###\n".replaceAll("#", text), gd1);
appendPara("##############\n".replaceAll("#", text), gd2);
appendPara("#######\n".replaceAll("#", text), gd3);
appendPara("###########\n".replaceAll("#", text), gd4);
}
private void appendPara(String string, Drawable gd) {
final int start = mEditable.length();
mEditable.append(string);
final int end = mEditable.length();
mEditable.setSpan(new MySpan(gd), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}

I don't think it is possible with android's default TextView.
You can create your own view, by extending you class with View, and use methods like Canvas.drawText(...) and Canvas.drawBitmap(...) in the view's onDraw() method, to create each paragraph with a different gradient.
This can also help you control line-height and paragraph-spacing.

As, you need multiple gradients for your multiple Paragraphs then why not to have one TextView per Paragraph and setting unique gradient for each TextView.
If you donot know how many paragraphs you will have,
You must create a LinearLayout whose height is wrap-content and at run time for every paragraph create a TextView with fill_parent width and wrap_context height and add that TextView to your parent (LinearLayout) view. and in code set backgrounds to your TextViews.

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 Java Vertical scroll view size

I have a very simple activity with a vertical scroll view (mainscreen.xml below).
This vertical scroll view has a linear layout child view.
I dynamically add text views as children to this linear layout (populateLinlayWithTextViews() below).
Later, after these text view children have been added, I can scroll the vertical scroll view (onScrollChange() below).
And I can measure the visible part of the vertical scroll view (getHeight()).
My problem is that I need to know earlier than at scroll time what is the maximum height of the visible part of the vertical scroll view.
mainscreen.xml
<?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">
<ScrollView
android:id="#+id/scrollview"
android:layout_height="wrap_content"
android:layout_width="match_parent">
<LinearLayout
android:id="#+id/linlay"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
</LinearLayout>
</ScrollView>
</LinearLayout>
MyActivity.java
protected void onCreate(#Nullable Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.mainscreen);
Resources resources = getResources();
int n_statusBarHeight = resources.getIdentifier( "status_bar_height", "dimen", "android"); // 36px
populateLinlayWithTextViews();
// HERE
ScrollView sv = sv = findViewById(R.id.scrollview);
int n_svVisiblePartHeight = sv.getHeight(); // 0px
View.OnScrollChangeListener onScrollChangedListener
= new View.OnScrollChangeListener()
{
#Override
public void onScrollChange(View v, int scrollX, int scrollY, int oldScrollX, int oldScrollY)
{
ScrollView sv = sv = findViewById(R.id.scrollview);
int n_svVisiblePartHeight = sv.getHeight(); // 680px
}
}
sv = findViewById(R.id.scrollview);
sv.setOnScrollChangeListener(onScrollChangedListener);
}
public void populateLinlayWithTextViews()
{
LinearLayout linlay = findViewById(R.id.linlay);
int n_viewHeight = 81; // px
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
n_viewHeight);
for(int n_i=0 ; n_i<20 ; n_i++)
{
TextView tvNew = new TextView(m_app);
tvNew.setText(n_i + " - test");
tvNew.setTextColor(Color.rgb(0, 0, 0)); // black
tvNew.setTextSize(TypedValue.COMPLEX_UNIT_SP, 40);
tvNew.setLayoutParams(params);
linlay.addView(tvNew, linlay.getChildCount());
}
}
When I scroll the vertical scroll view, I can see that n_svVisiblePartHeight's value is 680px.
But I need to know this value earlier // HERE.
But // HERE, the value I get is 0px.
Can you help me?
EDIT 1
I need to know this height because:
example 1: I would like to know initially the index of the text view that will be at the bottom of the (visible) screen after I have populated
the linear layout with text views ;
example 2: given this height X I'm looking for, let's say that I would like 10 text views to be visible initially, I would like to determine X/10 which will be the height of the text views, so that I can put it dynamically in n_viewHeight.
EDIT 2
I just need to know the height of the red arrow below, a soon as I can in the lifecycle of the activity and preferably before I add text views to the linear layout or right after (// HERE):
EDIT 3
I don't know if theoretically that's true:
n_scrollViewVisibleHeightInPx =
n_displayHeightInPx
- n_actionBarHeight
- n_navigationBarHeightInPx
+ n_statusBarHeightInPx;
... but practically n_scrollViewVisibleHeightInPx is the height of the space designated by the red arrow in the image above.
To get these various heights, below are the methods I used:
private int returnStatusBarHeightInPx()
{
int n_statusBarHeightInPx = -1;
Resources resources = null;
int n_idStatusBarHeight = -1;
resources = getResources();
n_idStatusBarHeight = resources.getIdentifier( "status_bar_height", "dimen", "android");
if(n_idStatusBarHeight > 0)
{
n_statusBarHeightInPx = getResources().getDimensionPixelSize(n_idStatusBarHeight);
}
else
{
n_statusBarHeightInPx = 0;
}
return n_statusBarHeightInPx;
}
private int returnNavigationBarHeightInPx()
{
int n_navigationBarHeightInPx = -1;
Resources resources = null;
int n_idNavigationBarHeight = -1;
resources = getResources();
n_idNavigationBarHeight = resources.getIdentifier("navigation_bar_height", "dimen", "android");
if(n_idNavigationBarHeight > 0)
{
n_navigationBarHeightInPx = getResources().getDimensionPixelSize(n_idNavigationBarHeight);
}
else
{
n_navigationBarHeightInPx = 0;
}
return n_navigationBarHeightInPx;
}
private int returnActionBarHeightInPx()
{
TypedValue typedValue = null;
int n_actionBarHeightInPx = -1;
typedValue = new TypedValue();
if(getTheme().resolveAttribute(android.R.attr.actionBarSize, typedValue, true))
{
n_actionBarHeightInPx =
TypedValue.complexToDimensionPixelSize(
typedValue.data,
getResources().getDisplayMetrics());
}
return n_actionBarHeightInPx;
}
private int returnDisplayHeightInPx()
{
DisplayMetrics displayMetrics = null;
int n_displayHeightInPx = -1;
displayMetrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
n_displayHeightInPx = displayMetrics.heightPixels;
return n_displayHeightInPx;
}
Why do you want to know it in "//here". You do not use that variable there. Your code does not explain, why it is not possible, to define the variable on scroll time.

Setting image resourse by obtaining tag position for an imageview android

I am making use of recycler view. I have a layout that is highlighted in light red,this layout is included for each item in the recycler view. The light red layout is placed over the background image. I am using setTag method to identify the clicks of the buttons in red layout. That is working properly when i click i get the position. The problem is i want to change the image at specific position.
For example : Consider the heart button. I have set a tag on it like this.
heartButton = findViewById(id);
heartButton.setTag(position);
now i get the position by using the getTag method. But now i want to change the image of the heartButton at the a specific position. Is there something like
heartButton.getTag(position).setImageResouce(drawable);
If not how do i do this then.
use setBackgroundResource(R.drawable.XXX)
http://developer.android.com/reference/android/view/View.html#setBackgroundResource(int)
Proper way to do this is,
You have to keep the state of the heart button stored in the model(POJO) which is passed to custom adapter.
e.g.
class ModelListItem{
public static final int HEART=1,BROKEN_HEART=2;
int heartButtonState;
}
Now in onClick() of heart button, get that object from adapter using position,cosidering you have already figured it out on how to preserve position from heart button
ModelListItem item = (ModelListItem)adapter.getItem(position)
Change the state of heart button;
item.setHeartButtonState(ModelListItem.BROKEN_HEART);
adapter.notifyDatasetChanged();
You already know below explaination but just in case
To work this properly,in your getView methode of adapter you need to put the check on heartButtonState(); and use appropriate image resource.
getView(BOILERPLATE){
BOILERPLATE
switch(item.getheartButtonState()){
case ModelItemList.HEART:
heartbutton.setImageResource(heart_image);
break;
case ModelItemList.BROKEN_HEART:
heartbutton.setImageResource(broken_heart_image);
break;
}
I made a custom click listener and updated the like in the setter getter.But this works only when the view has been moved out of the view (i think it is the scrapeview)
The Setter Getter Class
public class DemoData {
int background;
boolean liked;
public DemoData(int background) {
this.background = background;
}
public int getBackground() {
return background;
}
// public void setBackground(int background) {
// this.background = background;
// }
public boolean isLiked() {
return liked;
}
public void setLiked(boolean liked) {
this.liked = liked;
}
}
The onBindViewHolder function of the recycler view
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
background = (ImageView) holder.view.findViewById(R.id.image);
layout = (LinearLayout) holder.view.findViewById(R.id.layout);
delete = (ImageView) layout.findViewById(R.id.delete);
lock = (ImageView) layout.findViewById(R.id.lock);
delete.setTag("delete_"+position);
lock.setTag("lock_"+position);
if(Constants.demoDatas.get(position).isLiked()){
delete.setImageResource(R.drawable.ic_launcher);
}
else{
delete.setImageResource(android.R.drawable.ic_delete);
}
delete.setOnClickListener(new CustomClickListener(position));
lock.setOnClickListener(new CustomClickListener(position));
}
The custom click listener is as below
public class CustomClickListener implements View.OnClickListener {
int position;
public CustomClickListener(int position) {
this.position = position;
}
#Override
public void onClick(View v) {
String tag = (String) v.getTag();
String identifier[] = tag.split("_");
// this line saves my state in the Setter Getter Class
Constants.demoDatas.get(position).setLiked(true);
}
}

StateListDrawable to switch colorfilters

I want to create custom buttons to use in a TabHost. I haven been trying to just use the same image resource (png), but have the colorfilter change depending on the state. So I made this bit to serve as the layout for the custom button:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent" android:layout_height="fill_parent">
<ImageView android:id="#+id/tab_icon"
android:layout_centerInParent="true" android:layout_alignParentTop="true"
android:layout_centerHorizontal="true" android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView android:id="#+id/tab_text" android:layout_below="#id/tab_icon"
android:layout_centerHorizontal="true" android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</RelativeLayout>
In my activity, I add the tabs like this:
tabHost.addTab(tabHost.newTabSpec(TAB_NAME_NEWS).setIndicator(buildTab(R.drawable.tab_icon_news, R.string.news))
.setContent(newsIntent));
And this is the 'buildTab' method:
private final static int[] SELECTED = new int[] { android.R.attr.state_selected };
private final static int[] IDLE = new int[] { -android.R.attr.state_selected };
private View buildTab(int icon, int label) {
LayoutInflater inflater = LayoutInflater.from(this);
View view = inflater.inflate(R.layout.tab_button, null);
StateListDrawable drawable = new StateListDrawable();
Drawable selected = getResources().getDrawable(icon);
selected.mutate();
selected.setBounds(0, 0, selected.getIntrinsicWidth(), selected.getIntrinsicHeight());
selected.setColorFilter(new LightingColorFilter(0xFFFFFFFF, 0x0000FF00));
drawable.addState(SELECTED, selected);
Drawable idle = getResources().getDrawable(icon);
idle.mutate();
idle.setColorFilter(new LightingColorFilter(0xFFFFFFFF, 0x000000FF));
drawable.addState(IDLE, idle);
((ImageView) view.findViewById(R.id.tab_icon)).setImageDrawable(drawable);
((TextView) view.findViewById(R.id.tab_text)).setText(getString(label));
return view;
}
In the selected state, the image should be completely green (0x0000FF00), and in the non-selected state, it should be blue (0x000000FF).
The problem is that the colorfilters appear to be be completely ignored. I can not see the colors change under any circumstances.
I've also tried to get the same result by setting the android:tint property on the <ImageView/>, but apparently you cannot use a reference to a <selector> there, since it throws a NumberFormatException.
I don't see what I'm doing wrong so any help would be appreciated.
OK, I never got the above code to work, so here's what I ended up doing.
First, I subclassed LayerDrawable:
public class StateDrawable extends LayerDrawable {
public StateDrawable(Drawable[] layers) {
super(layers);
}
#Override
protected boolean onStateChange(int[] states) {
for (int state : states) {
if (state == android.R.attr.state_selected) {
super.setColorFilter(Color.argb(255, 255, 195, 0), PorterDuff.Mode.SRC_ATOP);
} else {
super.setColorFilter(Color.GRAY, PorterDuff.Mode.SRC_ATOP);
}
}
return super.onStateChange(states);
}
#Override
public boolean isStateful() {
return true;
}
}
I changed the buildTab() method to the following:
private View buildTab(int icon, int label) {
LayoutInflater inflater = LayoutInflater.from(this);
View view = inflater.inflate(R.layout.tab_button, null);
((ImageView) view.findViewById(R.id.tab_icon)).setImageDrawable(new StateDrawable(new Drawable[] { getResources()
.getDrawable(icon) }));
((TextView) view.findViewById(R.id.tab_text)).setText(getString(label));
return view;
}
I still add the tabs like this:
Intent fooIntent = new Intent().setClass(this, FooActivity.class);
tabHost.addTab(tabHost.newTabSpec(TAB_NAME_INFO).setIndicator(buildTab(R.drawable.tab_icon_info, R.string.info)).setContent(infoIntent));
This works for me, compatible with android 1.6.
Couldn't solve it with applying a colorfilter directly to the drawable either. What worked for me was getting the image as a Bitmap, create an empty second one with same measures, define a canvas for the second one, apply that colorfilter to a paint object and draw the first bitmap on the second one. Finally create a BitmapDrawable from the new Bitmap and you're done. Here is the code
ImageButton imageButton = (ImageButton)findViewById(R.id.aga);
Bitmap one = BitmapFactory.decodeResource(getResources(), R.drawable.pen_circle);
Bitmap oneCopy = Bitmap.createBitmap(one.getWidth(), one.getHeight(), Config.ARGB_8888);
Canvas c = new Canvas(oneCopy);
Paint p = new Paint();
p.setColorFilter(new LightingColorFilter(Color.CYAN, 1));
c.drawBitmap(one, 0, 0, p);
StateListDrawable states = new StateListDrawable();
states.addState(new int[] {android.R.attr.state_pressed}, new BitmapDrawable(oneCopy));
states.addState(new int[] { }, imageButton.getDrawable());
imageButton.setImageDrawable(states);
This is my class, hacked to support ColorFilter:
Usage:
final Drawable icon = getResources().getDrawable(iconResId);
final Drawable filteredIcon = // this is important
icon.getConstantState().newDrawable();
final FilterableStateListDrawable selectorDrawable =
new FilterableStateListDrawable();
selectorDrawable.addState(ICON_STATE_SELECTED, filteredIcon,
new PorterDuffColorFilter(mIconOverlayColor, PorterDuff.Mode.SRC_ATOP));
selectorDrawable.addState(ICON_STATE_DEFAULT, icon);
As you see the ColorFilter is not applied directly to the drawable, it is associated to it while adding a state to the selector Drawable.
What's important here is that
you need to create a new drawable from the constant state or you'll modify the constant state and thus any instance of that drawable around your activity.
you need to use my custom addState method, it has the same name of the framework method addState but I've added an additional argument (ColorFilter). This method does NOT exist in the framework superclass!
The code (dirty, but work for me):
/**
* This is an extension to {#link android.graphics.drawable.StateListDrawable} that workaround a bug not allowing
* to set a {#link android.graphics.ColorFilter} to the drawable in one of the states., it add a method
* {#link #addState(int[], android.graphics.drawable.Drawable, android.graphics.ColorFilter)} for that purpose.
*/
public class FilterableStateListDrawable extends StateListDrawable {
private int currIdx = -1;
private int childrenCount = 0;
private SparseArray<ColorFilter> filterMap;
public FilterableStateListDrawable() {
super();
filterMap = new SparseArray<ColorFilter>();
}
#Override
public void addState(int[] stateSet, Drawable drawable) {
super.addState(stateSet, drawable);
childrenCount++;
}
/**
* Same as {#link #addState(int[], android.graphics.drawable.Drawable)}, but allow to set a colorFilter associated to this Drawable.
*
* #param stateSet - An array of resource Ids to associate with the image.
* Switch to this image by calling setState().
* #param drawable -The image to show.
* #param colorFilter - The {#link android.graphics.ColorFilter} to apply to this state
*/
public void addState(int[] stateSet, Drawable drawable, ColorFilter colorFilter) {
// this is a new custom method, does not exist in parent class
int currChild = childrenCount;
addState(stateSet, drawable);
filterMap.put(currChild, colorFilter);
}
#Override
public boolean selectDrawable(int idx) {
if (currIdx != idx) {
setColorFilter(getColorFilterForIdx(idx));
}
boolean result = super.selectDrawable(idx);
// check if the drawable has been actually changed to the one I expect
if (getCurrent() != null) {
currIdx = result ? idx : currIdx;
if (!result) {
// it has not been changed, meaning, back to previous filter
setColorFilter(getColorFilterForIdx(currIdx));
}
} else if (getCurrent() == null) {
currIdx = -1;
setColorFilter(null);
}
return result;
}
private ColorFilter getColorFilterForIdx(int idx) {
return filterMap != null ? filterMap.get(idx) : null;
}
}
I've opened a bug about this: https://code.google.com/p/android/issues/detail?id=60183
UPDATE: the bug has been fixed in the framework, since Lollipop I think.
I think the fix commit is this: https://android.googlesource.com/platform/frameworks/base/+/729427d%5E!/
or on Github: https://github.com/android/platform_frameworks_base/commit/729427d451bc4d4d268335b8dc1ff6404bc1c91e
My workaround should still work after Lollipop, it just don't use the fix by Google.
Here is my variation of Mopper's code. The idea is that ImageView gets color filter when user touches it, and color filter is removed when user stops touching it.
class PressedEffectStateListDrawable extends StateListDrawable {
private int selectionColor;
public PressedEffectStateListDrawable(Drawable drawable, int selectionColor) {
super();
this.selectionColor = selectionColor;
addState(new int[] { android.R.attr.state_pressed }, drawable);
addState(new int[] {}, drawable);
}
#Override
protected boolean onStateChange(int[] states) {
boolean isStatePressedInArray = false;
for (int state : states) {
if (state == android.R.attr.state_pressed) {
isStatePressedInArray = true;
}
}
if (isStatePressedInArray) {
super.setColorFilter(selectionColor, PorterDuff.Mode.MULTIPLY);
} else {
super.clearColorFilter();
}
return super.onStateChange(states);
}
#Override
public boolean isStateful() {
return true;
}
}
usage:
Drawable drawable = new FastBitmapDrawable(bm);
imageView.setImageDrawable(new PressedEffectStateListDrawable(drawable, 0xFF33b5e5));
Here is my variation of #Malachiasz code, this lets you pick whatever combination of states and colors to apply to the base drawable.
public class ColorFilteredStateDrawable extends StateListDrawable {
private final int[][] states;
private final int[] colors;
public ColorFilteredStateDrawable(Drawable drawable, int[][] states, int[] colors) {
super();
drawable.mutate();
this.states = states;
this.colors = colors;
for (int i = 0; i < states.length; i++) {
addState(states[i], drawable);
}
}
#Override
protected boolean onStateChange(int[] states) {
if (this.states != null) {
for (int i = 0; i < this.states.length; i++) {
if (StateSet.stateSetMatches(this.states[i], states)) {
super.setColorFilter(this.colors[i], PorterDuff.Mode.MULTIPLY);
return super.onStateChange(states);
}
}
super.clearColorFilter();
}
return super.onStateChange(states);
}
#Override
public boolean isStateful() {
return true;
}
}

Display Animated GIF

I want to display animated GIF images in my aplication.
As I found out the hard way Android doesn't support animated GIF natively.
However it can display animations using AnimationDrawable:
Develop > Guides > Images & Graphics > Drawables Overview
The example uses animation saved as frames in application resources but what I need is to display animated gif directly.
My plan is to break animated GIF to frames and add each frame as drawable to AnimationDrawable.
Does anyone know how to extract frames from animated GIF and convert each of them into Drawable?
Android actually can decode and display animated GIFs, using android.graphics.Movie class.
This is not too much documented, but is in SDK Reference. Moreover, it is used in Samples in ApiDemos in BitmapDecode example with some animated flag.
UPDATE:
Use glide:
dependencies {
implementation 'com.github.bumptech.glide:glide:4.9.0'
}
usage:
Glide.with(context).load(GIF_URI).into(new DrawableImageViewTarget(IMAGE_VIEW));
see docs
also put (main/assets/htmls/name.gif) [with this html adjust to the size]
<html style="margin: 0;">
<body style="margin: 0;">
<img src="name.gif" style="width: 100%; height: 100%" />
</body>
</html>
declare in your Xml for example like this (main/res/layout/name.xml): [you define the size, for example]
<WebView
android:layout_width="70dp"
android:layout_height="70dp"
android:id="#+id/webView"
android:layout_gravity="center_horizontal" />
in your Activity put the next code inside of onCreate
web = (WebView) findViewById(R.id.webView);
web.setBackgroundColor(Color.TRANSPARENT); //for gif without background
web.loadUrl("file:///android_asset/htmls/name.html");
if you want load dynamically you have to load the webview with data:
// or "[path]/name.gif" (e.g: file:///android_asset/name.gif for resources in asset folder), and in loadDataWithBaseURL(), you don't need to set base URL, on the other hand, it's similar to loadData() method.
String gifName = "name.gif";
String yourData = "<html style=\"margin: 0;\">\n" +
" <body style=\"margin: 0;\">\n" +
" <img src=" + gifName + " style=\"width: 100%; height: 100%\" />\n" +
" </body>\n" +
" </html>";
// Important to add this attribute to webView to get resource from outside.
webView.getSettings().setAllowFileAccess(true);
// Notice: should use loadDataWithBaseURL. BaseUrl could be the base url such as the path to asset folder, or SDCard or any other path, where your images or the other media resides related to your html
webView.loadDataWithBaseURL("file:///android_asset/", yourData, "text/html", "utf-8", null);
// Or if you want to load image from SD card or where else, here is the idea.
String base = Environment.getExternalStorageDirectory().getAbsolutePath().toString();
webView.loadDataWithBaseURL(base + '/', yourData, "text/html", "utf-8", null);
suggestion: is better load gif with static images for more information check https://developer.android.com/reference/android/graphics/drawable/AnimationDrawable.html
That's it, I hope you help.
Currently we can use Glide https://github.com/bumptech/glide
I solved the problem by splitting gif animations into frames before saving it to phone, so I would not have to deal with it in Android.
Then I download every frame onto phone, create Drawable from it and then create AnimationDrawable - very similar to example from my question
i found a very easy way, with a nice and simple working example here
display animated widget
Before getting it working there are some chages to do do in the code
IN THE FOLLOWING
#Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceStated);
setContentView(new MYGIFView());
}
}
just replace
setContentView(new MYGIFView());
in
setContentView(new MYGIFView(this));
AND IN
public GIFView(Context context) {
super(context);
Provide your own gif animation file
is = context.getResources().openRawResource(R.drawable.earth);
movie = Movie.decodeStream(is);
}
REPLACE THE FIRST LINE IN
public MYGIFView(Context context) {
according to the name of the class...
after done this little changes it should work as for me...
hope this help
Glide 4.6
1. To Load gif
GlideApp.with(context)
.load(R.raw.gif) // or url
.into(imageview);
2. To get the file object
GlideApp.with(context)
.asGif()
.load(R.raw.gif) //or url
.into(new SimpleTarget<GifDrawable>() {
#Override
public void onResourceReady(#NonNull GifDrawable resource, #Nullable Transition<? super GifDrawable> transition) {
resource.start();
//resource.setLoopCount(1);
imageView.setImageDrawable(resource);
}
});
Ways to show animated GIF on Android:
Movie class. As mentioned above, it's fairly buggy.
WebView. It's very simple to use and usually works. But sometimes it starts to misbehave, and it's always on some obscure devices you don't have. Plus, you can’t use multiple instances in any kind of list views, because it does things to your memory. Still, you might consider it as a primary approach.
Custom code to decode gifs into bitmaps and show them as Drawable or ImageView. I'll mention two libraries:
https://github.com/koral--/android-gif-drawable - decoder is implemented in C, so it's very efficient.
https://code.google.com/p/giffiledecoder - decoder is implemented in Java, so it's easier to work with. Still reasonably efficient, even with large files.
You'll also find many libraries based on GifDecoder class. That's also a Java-based decoder, but it works by loading the entire file into memory, so it's only applicable to small files.
I had a really hard time to have animated gif working in Android. I only had following two working:
WebView
Ion
WebView works OK and really easy, but the problem is it makes the view loads slower and the app would be unresponsive for a second or so. I did not like that. So I have tried different approaches (DID NOT WORK):
ImageViewEx is deprecated!
picasso did not load animated gif
android-gif-drawable looks great, but it caused some wired NDK issues in my project. It caused my local NDK library stop working, and I was not able to fix it
I had some back and forth with Ion; Finally, I have it working, and it is really fast :-)
Ion.with(imgView)
.error(R.drawable.default_image)
.animateGif(AnimateGifMode.ANIMATE)
.load("file:///android_asset/animated.gif");
Glide
Image Loader Library for Android, recommended by Google.
Glide is quite similar to Picasso but this is much faster than Picasso.
Glide consumes less memory than Picasso.
What that Glide has but Picasso doesn't
An ability to load GIF Animation to a simple ImageView might be the most interesting feature of Glide. And yes, you can't do that with Picasso.
Some important links-
https://github.com/bumptech/glide
http://inthecheesefactory.com/blog/get-to-know-glide-recommended-by-google/en
Use ImageViewEx, a library that makes using a gif as easy as using an ImageView.
Try this, bellow code display gif file in progressbar
loading_activity.xml(in Layout folder)
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#ffffff" >
<ProgressBar
android:id="#+id/progressBar"
style="?android:attr/progressBarStyleLarge"
android:layout_width="70dp"
android:layout_height="70dp"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:indeterminate="true"
android:indeterminateDrawable="#drawable/custom_loading"
android:visibility="gone" />
</RelativeLayout>
custom_loading.xml(in drawable folder)
here i put black_gif.gif(in drawable folder), you can put your own gif here
<?xml version="1.0" encoding="utf-8"?>
<animated-rotate xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="#drawable/black_gif"
android:pivotX="50%"
android:pivotY="50%" />
LoadingActivity.java(in res folder)
public class LoadingActivity extends Activity {
ProgressBar bar;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_loading);
bar = (ProgressBar) findViewById(R.id.progressBar);
bar.setVisibility(View.VISIBLE);
}
}
Nobody has mentioned the Ion or Glide library. they work very well.
It's easier to handle compared to a WebView.
I have had success with the solution proposed within this article, a class called GifMovieView, which renders a View which can then be displayed or added to a specific ViewGroup. Check out the other methods presented in parts 2 and 3 of the specified article.
The only drawback to this method is that the antialiasing on the movie is not that good (must be a side-effect of using the "shady" Android Movie Class). You are then better off setting the background to a solid color within your animated GIF.
Some thoughts on the BitmapDecode example... Basically it uses the ancient, but rather featureless Movie class from android.graphics.
On recent API versions you need to turn off hardware acceleration, as described here. It was segfaulting for me otherwise.
<activity
android:hardwareAccelerated="false"
android:name="foo.GifActivity"
android:label="The state of computer animation 2014">
</activity>
Here is the BitmapDecode example shortened with only the GIF part. You have to make your own Widget (View) and draw it by yourself. Not quite as powerful as an ImageView.
import android.app.Activity;
import android.content.Context;
import android.graphics.*;
import android.os.*;
import android.view.View;
public class GifActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(new GifView(this));
}
static class GifView extends View {
Movie movie;
GifView(Context context) {
super(context);
movie = Movie.decodeStream(
context.getResources().openRawResource(
R.drawable.some_gif));
}
#Override
protected void onDraw(Canvas canvas) {
if (movie != null) {
movie.setTime(
(int) SystemClock.uptimeMillis() % movie.duration());
movie.draw(canvas, 0, 0);
invalidate();
}
}
}
}
2 other methods, one with ImageView another with WebView can be found in this fine tutorial. The ImageView method uses the Apache licensed android-gifview from Google Code.
#PointerNull gave good solution, but it is not perfect. It doesn't work on some devices with big files and show buggy Gif animation with delta frames on pre ICS version.
I found solution without this bugs. It is library with native decoding to drawable: koral's android-gif-drawable.
For only android API (Android Pie)28 and + use AnimatedImageDrawable as
// ImageView from layout
val ima : ImageView = findViewById(R.id.img_gif)
// create AnimatedDrawable
val decodedAnimation = ImageDecoder.decodeDrawable(
// create ImageDecoder.Source object
ImageDecoder.createSource(resources, R.drawable.tenor))
// set the drawble as image source of ImageView
ima.setImageDrawable(decodedAnimation)
// play the animation
(decodedAnimation as? AnimatedImageDrawable)?.start()
XML code, add a ImageView
<ImageView
android:id="#+id/img_gif"
android:background="#drawable/ic_launcher_background" <!--Default background-->
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
android:layout_width="200dp"
android:layout_height="200dp" />
AnimatedImageDrawable is a child of Drawable and created by ImageDecoder.decodeDrawable
ImageDecoder.decodeDrawable which further required the instance of ImageDecoder.Source created by ImageDecoder.createSource.
ImageDecoder.createSource can only take source as a name, ByteBuffer, File, resourceId, URI, ContentResolver to create source object and uses it to create AnimatedImageDrawable as Drawable (polymorphic call)
static ImageDecoder.Source createSource(AssetManager assets, String fileName)
static ImageDecoder.Source createSource(ByteBuffer buffer)
static ImageDecoder.Source createSource(File file)
static ImageDecoder.Source createSource(Resources res, int resId)
static ImageDecoder.Source createSource(ContentResolver cr, Uri uri)
Note: You can also create Bitmap using ImageDecoder#decodeBitmap.
Output:
AnimatedDrawable also supports resizing, frame and color manipulation
Put it into a WebView, it has to be able to display it correctly, since the default browser supports gif files. (Froyo+, if i am not mistaken)
There are two options to load animated gifs into our Android apps
1)Using Glide to load the gif into an ImageView.
String urlGif = "https://cdn.dribbble.com/users/263558/screenshots/1337078/dvsd.gif";
//add Glide implementation into the build.gradle file.
ImageView imageView = (ImageView)findViewById(R.id.imageView);
Uri uri = Uri.parse(urlGif);
Glide.with(getApplicationContext()).load(uri).into(imageView);
2) Using an html to load the gif into a WebView
Create the html with the address to the .gif file:
<html style="margin: 0;">
<body style="margin: 0;">
<img src="https://..../myimage.gif" style="width: 100%; height: 100%" />
</body>
</html>
store this file into the assets directory:
The load this html into the WebView of your application:
WebView webView = (WebView)findViewById(R.id.webView);
webView = (WebView) findViewById(R.id.webView);
webView.loadUrl("file:///android_asset/html/webpage_gif.html");
Heres is a complete example of this two options.
I think the better library to handle gif files is this one: by koral
Used it and i'm successful and this library is dedicated to GIF'S; but where as the picasso and glide are general purpose image framework; so i think the developers of this library have entirely concentrated on gif files
Use fresco. Here's how to do it:
http://frescolib.org/docs/animations.html
Here's the repo with the sample:
https://github.com/facebook/fresco/tree/master/samples/animation
Beware fresco does not support wrap content!
Just wanted to add that the Movie class is now deprecated.
This class was deprecated in API level P.
It is recommended to use this
AnimatedImageDrawable
Drawable for drawing animated images (like GIF).
Similar to what #Leonti said, but with a little more depth:
What I did to solve the same problem was open up GIMP, hide all layers except for one, export it as its own image, and then hide that layer and unhide the next one, etc., until I had individual resource files for each one. Then I could use them as frames in the AnimationDrawable XML file.
Something I did for showing gifs in apps. I extended ImageView so people can use its attributes freely. It can show gifs from url or from the assets directory.
The library also makes it easy for extending classes to inherit from it and extend it to support different methods to initialize the gif.
https://github.com/Gavras/GIFView
There's a little guide on the github page.
It was also published on Android Arsenal:
https://android-arsenal.com/details/1/4947
Use example:
From XML:
<com.whygraphics.gifview.gif.GIFView xmlns:gif_view="http://schemas.android.com/apk/res-auto"
android:id="#+id/main_activity_gif_vie"
android:layout_width="200dp"
android:layout_height="200dp"
android:scaleType="center"
gif_view:gif_src="url:http://pop.h-cdn.co/assets/16/33/480x264/gallery-1471381857-gif-season-2.gif" />
In the activity:
GIFView mGifView = (GIFView) findViewById(R.id.main_activity_gif_vie);
mGifView.setOnSettingGifListener(new GIFView.OnSettingGifListener() {
#Override
public void onSuccess(GIFView view, Exception e) {
Toast.makeText(MainActivity.this, "onSuccess()", Toast.LENGTH_SHORT).show();
}
#Override
public void onFailure(GIFView view, Exception e) {
}
});
Setting the gif programmatically:
mGifView.setGifResource("asset:gif1");
Easiest way - Can be consider the below code
We can take advantage of Imageview setImageResource , refer below code for the same.
The below code can be used to show the image like gif incase if you have the multiple split image of gif. Just split the gif into individual png from a online tool and put image in the drawable like the below order
image_1.png, image_2.png, etc.
Have the handler to change the image dynamically.
int imagePosition = 1;
Handler handler = new Handler();
Runnable runnable = new Runnable() {
public void run() {
updateImage();
}
};
public void updateImage() {
appInstance.runOnUiThread(new Runnable() {
#Override
public void run() {
int resId = getResources().getIdentifier("image_" + imagePosition, "drawable", appInstance.getPackageName());
gifImageViewDummy.setImageResource(resId);
imagePosition++;
//Consider you have 30 image for the anim
if (imagePosition == 30) {
//this make animation play only once
handler.removeCallbacks(runnable);
} else {
//You can define your own time based on the animation
handler.postDelayed(runnable, 50);
}
//to make animation to continue use below code and remove above if else
// if (imagePosition == 30)
//imagePosition = 1;
// handler.postDelayed(runnable, 50);
//
}
});
}
The easy way to display animated GIF directly from URL to your app layout is to use WebView class.
Step 1:
In your layout XML
<WebView
android:id="#+id/webView"
android:layout_width="50dp"
android:layout_height="50dp"
/>
Step 2: In your Activity
WebView wb;
wb = (WebView) findViewById(R.id.webView);
wb.loadUrl("https://.......);
Step 3: In your Manifest.XML make Internet permission
<uses-permission android:name="android.permission.INTERNET" />
Step 4: In case you want to make your GIF background transparent and make GIF fit to your Layout
wb.setBackgroundColor(Color.TRANSPARENT);
wb.getSettings().setLoadWithOverviewMode(true);
wb.getSettings().setUseWideViewPort(true);
If you want to use Glide for loading gif:
Glide.with(this)
.asGif()
.load(R.raw.onboarding_layers) //Your gif resource
.apply(RequestOptions.diskCacheStrategyOf(DiskCacheStrategy.NONE))
.listener(new RequestListener<GifDrawable>() {
#Override
public boolean onLoadFailed(#Nullable #org.jetbrains.annotations.Nullable GlideException e, Object model, Target<GifDrawable> target, boolean isFirstResource) {
return false;
}
#Override
public boolean onResourceReady(GifDrawable resource, Object model, Target<GifDrawable> target, DataSource dataSource, boolean isFirstResource) {
resource.setLoopCount(1);
return false;
}
})
.into((ImageView) view.findViewById(R.id.layer_icons));
To save resources there is glide library for.
Have no idea why to use anything else, especialy webview to show image only.
Glide is perfect and easy library that prepares animated drawable from gif and put it directly to imageview.
The logic of gifdrawable handle animation itself.
Gif have lzw ziped raw rgb data of an animation inside.
There is no reason for complicated usage of webview and manage more files to show just a gif file in app.
First of all the Android browser should support Animated GIFs. If it doesn't then it's a bug! Have a look at the issue trackers.
If you're displaying these animated GIFs outside of a browser it might be a different story. To do what you're asking would require external library that supports the decoding of Animated GIFs.
The first port of call would be to look at Java2D or JAI (Java Advanced Imaging) API, although I would be very surprised if Android Dalvik would support those libraries in your App.
public class Test extends GraphicsActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(new SampleView(this));
}
private static class SampleView extends View {
private Bitmap mBitmap;
private Bitmap mBitmap2;
private Bitmap mBitmap3;
private Bitmap mBitmap4;
private Drawable mDrawable;
private Movie mMovie;
private long mMovieStart;
// Set to false to use decodeByteArray
private static final boolean DECODE_STREAM = true;
private static byte[] streamToBytes(InputStream is) {
ByteArrayOutputStream os = new ByteArrayOutputStream(1024);
byte[] buffer = new byte[1024];
int len;
try {
while ((len = is.read(buffer)) >= 0) {
os.write(buffer, 0, len);
}
} catch (java.io.IOException e) {
}
return os.toByteArray();
}
public SampleView(Context context) {
super(context);
setFocusable(true);
java.io.InputStream is;
is = context.getResources().openRawResource(R.drawable.icon);
BitmapFactory.Options opts = new BitmapFactory.Options();
Bitmap bm;
opts.inJustDecodeBounds = true;
bm = BitmapFactory.decodeStream(is, null, opts);
// now opts.outWidth and opts.outHeight are the dimension of the
// bitmap, even though bm is null
opts.inJustDecodeBounds = false; // this will request the bm
opts.inSampleSize = 4; // scaled down by 4
bm = BitmapFactory.decodeStream(is, null, opts);
mBitmap = bm;
// decode an image with transparency
is = context.getResources().openRawResource(R.drawable.icon);
mBitmap2 = BitmapFactory.decodeStream(is);
// create a deep copy of it using getPixels() into different configs
int w = mBitmap2.getWidth();
int h = mBitmap2.getHeight();
int[] pixels = new int[w * h];
mBitmap2.getPixels(pixels, 0, w, 0, 0, w, h);
mBitmap3 = Bitmap.createBitmap(pixels, 0, w, w, h,
Bitmap.Config.ARGB_8888);
mBitmap4 = Bitmap.createBitmap(pixels, 0, w, w, h,
Bitmap.Config.ARGB_4444);
mDrawable = context.getResources().getDrawable(R.drawable.icon);
mDrawable.setBounds(150, 20, 300, 100);
is = context.getResources().openRawResource(R.drawable.animated_gif);
if (DECODE_STREAM) {
mMovie = Movie.decodeStream(is);
} else {
byte[] array = streamToBytes(is);
mMovie = Movie.decodeByteArray(array, 0, array.length);
}
}
#Override
protected void onDraw(Canvas canvas) {
canvas.drawColor(0xFFCCCCCC);
Paint p = new Paint();
p.setAntiAlias(true);
canvas.drawBitmap(mBitmap, 10, 10, null);
canvas.drawBitmap(mBitmap2, 10, 170, null);
canvas.drawBitmap(mBitmap3, 110, 170, null);
canvas.drawBitmap(mBitmap4, 210, 170, null);
mDrawable.draw(canvas);
long now = android.os.SystemClock.uptimeMillis();
if (mMovieStart == 0) { // first time
mMovieStart = now;
}
if (mMovie != null) {
int dur = mMovie.duration();
if (dur == 0) {
dur = 1000;
}
int relTime = (int) ((now - mMovieStart) % dur);
mMovie.setTime(relTime);
mMovie.draw(canvas, getWidth() - mMovie.width(), getHeight()
- mMovie.height());
invalidate();
}
}
}
}
class GraphicsActivity extends Activity {
// set to true to test Picture
private static final boolean TEST_PICTURE = false;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
#Override
public void setContentView(View view) {
if (TEST_PICTURE) {
ViewGroup vg = new PictureLayout(this);
vg.addView(view);
view = vg;
}
super.setContentView(view);
}
}
class PictureLayout extends ViewGroup {
private final Picture mPicture = new Picture();
public PictureLayout(Context context) {
super(context);
}
public PictureLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
public void addView(View child) {
if (getChildCount() > 1) {
throw new IllegalStateException(
"PictureLayout can host only one direct child");
}
super.addView(child);
}
#Override
public void addView(View child, int index) {
if (getChildCount() > 1) {
throw new IllegalStateException(
"PictureLayout can host only one direct child");
}
super.addView(child, index);
}
#Override
public void addView(View child, LayoutParams params) {
if (getChildCount() > 1) {
throw new IllegalStateException(
"PictureLayout can host only one direct child");
}
super.addView(child, params);
}
#Override
public void addView(View child, int index, LayoutParams params) {
if (getChildCount() > 1) {
throw new IllegalStateException(
"PictureLayout can host only one direct child");
}
super.addView(child, index, params);
}
#Override
protected LayoutParams generateDefaultLayoutParams() {
return new LayoutParams(LayoutParams.MATCH_PARENT,
LayoutParams.MATCH_PARENT);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
final int count = getChildCount();
int maxHeight = 0;
int maxWidth = 0;
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (child.getVisibility() != GONE) {
measureChild(child, widthMeasureSpec, heightMeasureSpec);
}
}
maxWidth += getPaddingLeft() + getPaddingRight();
maxHeight += getPaddingTop() + getPaddingBottom();
Drawable drawable = getBackground();
if (drawable != null) {
maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());
maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());
}
setMeasuredDimension(resolveSize(maxWidth, widthMeasureSpec),
resolveSize(maxHeight, heightMeasureSpec));
}
private void drawPict(Canvas canvas, int x, int y, int w, int h, float sx,
float sy) {
canvas.save();
canvas.translate(x, y);
canvas.clipRect(0, 0, w, h);
canvas.scale(0.5f, 0.5f);
canvas.scale(sx, sy, w, h);
canvas.drawPicture(mPicture);
canvas.restore();
}
#Override
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(mPicture.beginRecording(getWidth(), getHeight()));
mPicture.endRecording();
int x = getWidth() / 2;
int y = getHeight() / 2;
if (false) {
canvas.drawPicture(mPicture);
} else {
drawPict(canvas, 0, 0, x, y, 1, 1);
drawPict(canvas, x, 0, x, y, -1, 1);
drawPict(canvas, 0, y, x, y, 1, -1);
drawPict(canvas, x, y, x, y, -1, -1);
}
}
#Override
public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
location[0] = getLeft();
location[1] = getTop();
dirty.set(0, 0, getWidth(), getHeight());
return getParent();
}
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
final int count = super.getChildCount();
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (child.getVisibility() != GONE) {
final int childLeft = getPaddingLeft();
final int childTop = getPaddingTop();
child.layout(childLeft, childTop,
childLeft + child.getMeasuredWidth(),
childTop + child.getMeasuredHeight());
}
}
}
}

Categories