Android thread wait until visible - android

I've made a custom pie chart view that I want to animate starting when the pie chart is visible. Currently what I have is the pie chart animating but by the time you can actually see it on the screen the animation is half over. This is what I have:
public class SinglePieChart extends SurfaceView implements SurfaceHolder.Callback {
// Chart setting variables
private int emptyCircleCol, strokeColor, number, total;
// Paint for drawing custom view
private Paint circlePaint;
private RectF rect;
private Context context;
private AnimThread animThread;
private SurfaceHolder holder;
// animation variables
private float speed;
private float current = 0.0f;
private boolean percentsCalculated = false;
private float degree;
private int viewWidth, viewHeight;
public SinglePieChart(Context ctx, AttributeSet attrs) {
super(ctx, attrs);
context = ctx;
// Paint object for drawing in doDraw
circlePaint = new Paint();
circlePaint.setStyle(Style.STROKE);
circlePaint.setStrokeWidth(3);
circlePaint.setAntiAlias(true);
circlePaint.setDither(true);
rect = new RectF();
//get the attributes specified in attrs.xml using the name we included
TypedArray a = context.getTheme().obtainStyledAttributes(attrs,
R.styleable.DashboardChartSmall, 0, 0);
try {
//get the colors specified using the names in attrs.xml
emptyCircleCol = a.getColor(R.styleable.DashboardChartSmall_smCircleColor, 0xFF65676E); // light gray is default
strokeColor = a.getColor(R.styleable.DashboardChartSmall_smColor, 0xFF39B54A); // green is default
// Default number values
total = a.getInteger(R.styleable.DashboardChartSmall_smTotal, 1);
number = a.getInteger(R.styleable.DashboardChartSmall_smNumber, 0);
} finally {
a.recycle();
}
this.setZOrderOnTop(true);
holder = getHolder();
holder.setFormat(PixelFormat.TRANSPARENT);
holder.addCallback(this);
}
protected void calculateValues() {
degree = 360 * number / total;
percentsCalculated = true;
speed = 10 * number / total;
viewWidth = this.getMeasuredWidth();
viewHeight = this.getMeasuredHeight();
float top, left, bottom, right;
if (viewWidth < viewHeight) {
left = 4;
right = viewWidth - 4;
top = ((viewHeight - viewWidth) / 2) + 4;
bottom = viewHeight - top;
} else {
top = 4;
bottom = viewHeight - 4;
left = ((viewWidth - viewHeight) / 2) + 4;
right = viewWidth - left;
}
rect.set(left, top, right, bottom);
}
protected void doDraw(Canvas canvas) {
if (total == 0) {
// Number values are not ready
animThread.setRunning(false);
return;
}
if (!percentsCalculated) {
calculateValues();
}
// set the paint color using the circle color specified
float last = current;
float start = -90;
circlePaint.setColor(strokeColor);
canvas.drawArc(rect, start, (last > degree) ? degree : last, false, circlePaint);
start += (last > number) ? number : last;
last = (last < number) ? 0 : last - number;
circlePaint.setColor(emptyCircleCol);
if (current > 360) {
current = 360;
}
canvas.drawArc(rect, start, 360 - current, false, circlePaint);
current += speed;
if (last > 0 || number == 0) {
// we're done
animThread.setRunning(false);
}
}
public void setNumbers(int num, int tot) {
number = num;
total = tot;
invalidate();
requestLayout();
}
public void setColor(int col) {
strokeColor = col;
}
public void redraw() {
calculateValues();
animThread.setRunning(true);
invalidate();
requestLayout();
}
#Override
public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2, int arg3) {
}
#Override
public void surfaceCreated(SurfaceHolder arg0) {
animThread = new AnimThread(holder, context, this);
animThread.setRunning(true);
animThread.start();
}
#Override
public void surfaceDestroyed(SurfaceHolder arg0) {
animThread.setRunning(false);
boolean retry = true;
while(retry) {
try {
animThread.join();
retry = false;
} catch(Exception e) {
Log.v("Exception Occured", e.getMessage());
}
}
}
public class AnimThread extends Thread {
boolean mRun;
Canvas mcanvas;
SurfaceHolder surfaceHolder;
Context context;
SinglePieChart msurfacePanel;
public AnimThread(SurfaceHolder sholder, Context ctx, SinglePieChart spanel) {
surfaceHolder = sholder;
context = ctx;
mRun = false;
msurfacePanel = spanel;
}
void setRunning(boolean bRun) {
mRun = bRun;
}
#Override
public void run() {
super.run();
while (mRun) {
mcanvas = surfaceHolder.lockCanvas();
if (mcanvas != null) {
msurfacePanel.doDraw(mcanvas);
surfaceHolder.unlockCanvasAndPost(mcanvas);
}
}
}
}
}
Also if you see any programming errors, memory leaks, poor performing code, please let me know. I'm new to Android.
Here is the layout that uses the SinglePieChart class:
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
<com.davidscoville.vokab.views.elements.SinglePieChart
android:id="#+id/smallPieChart"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<TextView
android:id="#+id/dashSmNumber"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:textSize="25sp"
android:textColor="#FFFFFF" />
</RelativeLayout>
<TextView
android:id="#+id/dashSmLabel"
android:layout_width="match_parent"
android:layout_height="20dp"
android:textSize="14sp"
android:gravity="center"
android:textColor="#FFFFFF" />
</merge>

Alright I'm going with the my pie chart won't automatically animate and it will have a new function that the Activity will trigger to start animating once it's ready. I wish there was an easier way...

Alternatively you can use the animation framework(or nine old androids if you want to support older apis). This will allow you to animate properties on your view, in your case the start and current variables.
I'd set this to happen during onAttachedToWindow.
Note if you aren't doing a lot of other things in this pie chart a surfaceview might be overkill for your needs.

Related

Adding Marker Pins at touched Position using subsampling scale image view

I'm using "subsampling scale image view" in my app and I would like to dynamically add marker pins to a PinView when the user does a long click on it. The marker pin should appear at the clicked position.
I achieved that a marker pin appears after a long click, but at a wrong position. Here is the onCreateView method of my Fragment:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
pinCounter=0;
View rootView = inflater.inflate(R.layout.fragment_edit_plan, container, false);
src = getArguments().getString("src");
imageView = (PinView)rootView.findViewById(R.id.imageView);
imageView.setImage(ImageSource.uri(src));
imageView.isLongClickable();
imageView.isClickable();
imageView.hasOnClickListeners();
MapPins = new ArrayList();
imageView.setPins(MapPins);
imageView.setOnLongClickListener(this);
imageView.setOnTouchListener(this);
return rootView;
}
The Listener-Methods:
#Override
public boolean onLongClick(View view) {
if (view.getId() == R.id.imageView) {
Toast.makeText(getActivity(), "Long clicked "+lastKnownX+" "+lastKnownY, Toast.LENGTH_SHORT).show();
Log.d("EditPlanFragment","Scale "+imageView.getScale());
MapPins.add(new MapPin(lastKnownX,lastKnownY,pinCounter));
imageView.setPins(MapPins);
imageView.post(new Runnable(){
public void run(){
imageView.getRootView().postInvalidate();
}
});
return true;
}
return false;
}
#Override
public boolean onTouch(View v, MotionEvent event) {
if (v.getId()== R.id.imageView && event.getAction() == MotionEvent.ACTION_DOWN){
lastKnownX= event.getX();
lastKnownY= event.getY();
}
return false;
}
My XML-File:
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingTop="50dp"
>
<com.example.viktoriabock.phoenixversion1.PinView
android:id="#+id/imageView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:longClickable="true"
/>
And the PinView-Class:
public class PinView extends SubsamplingScaleImageView {
private PointF sPin;
ArrayList<MapPin> mapPins;
ArrayList<DrawPin> drawnPins;
Context context;
String tag = getClass().getSimpleName();
public PinView(Context context) {
this(context, null);
this.context = context;
}
public PinView(Context context, AttributeSet attr) {
super(context, attr);
this.context = context;
initialise();
}
public void setPins(ArrayList<MapPin> mapPins) {
this.mapPins = mapPins;
initialise();
invalidate();
}
public void setPin(PointF pin) {
this.sPin = pin;
}
public PointF getPin() {
return sPin;
}
private void initialise() {
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// Don't draw pin before image is ready so it doesn't move around during setup.
if (!isReady()) {
return;
}
drawnPins = new ArrayList<>();
Paint paint = new Paint();
paint.setAntiAlias(true);
float density = getResources().getDisplayMetrics().densityDpi;
for (int i = 0; i < mapPins.size(); i++) {
MapPin mPin = mapPins.get(i);
//Bitmap bmpPin = Utils.getBitmapFromAsset(context, mPin.getPinImgSrc());
Bitmap bmpPin = BitmapFactory.decodeResource(this.getResources(), R.drawable.pushpin_blue);
float w = (density / 420f) * bmpPin.getWidth();
float h = (density / 420f) * bmpPin.getHeight();
bmpPin = Bitmap.createScaledBitmap(bmpPin, (int) w, (int) h, true);
PointF vPin = sourceToViewCoord(mPin.getPoint());
//in my case value of point are at center point of pin image, so we need to adjust it here
float vX = vPin.x - (bmpPin.getWidth() / 2);
float vY = vPin.y - bmpPin.getHeight();
canvas.drawBitmap(bmpPin, vX, vY, paint);
//add added pin to an Array list to get touched pin
DrawPin dPin = new DrawPin();
dPin.setStartX(mPin.getX() - w / 2);
dPin.setEndX(mPin.getX() + w / 2);
dPin.setStartY(mPin.getY() - h / 2);
dPin.setEndY(mPin.getY() + h / 2);
dPin.setId(mPin.getId());
drawnPins.add(dPin);
}
}
public int getPinIdByPoint(PointF point) {
for (int i = drawnPins.size() - 1; i >= 0; i--) {
DrawPin dPin = drawnPins.get(i);
if (point.x >= dPin.getStartX() && point.x <= dPin.getEndX()) {
if (point.y >= dPin.getStartY() && point.y <= dPin.getEndY()) {
return dPin.getId();
}
}
}
return -1; //negative no means no pin selected
}
}
You're collecting the screen coordinates of the long press, then in the custom view you're converting them from source coordinates to screen coordinates. You need to convert the event coordinates into source coordinates when you get the tap event, using the view's viewToSourceCoord method.
There's an easier way to do this than using your lastKnown values - use your own GestureDetector.
For a full working example of a long press gesture detector, see the AdvancedEventHandlingActivity sample class.

Creating a circle with clickable dots in Android

I am trying to build a circle containing numerous dots that eventually will be clickable (as much as 108 dots to fill out the border of a circle).
What i have done so far is to create 108 imageviews like this:
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/circle_1"
android:src="#drawable/dot_complete"
android:layout_marginLeft="383dp"
android:layout_marginTop="214dp"
/>
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/circle_2"
android:src="#drawable/dot_complete"
android:layout_marginLeft="382dp"
android:layout_marginTop="214dp"
/>
<!-- And so on all the way up to 108 -->
The result looks like this
However i suspect this is a very bad method, so my question is what would be the better way to do this, considering i need to have onclickListener on each dot in order to show its info.
Thank you
I had a similar class laying around, with a small modification it can display three different types of drawables as "dots". The only thing you would have to do is to write the touch management.
Drawing 108 dots (three different types):
public class DotsView extends View {
private static final int dots = 108;
private static final int dotRadius = 20;
private Bitmap testBitmap1;
private Bitmap testBitmap2;
private Bitmap testBitmap3;
private RectF dotRect;
private Paint paint;
private int[] dotsStates = new int[dots];
public DotsView(Context context) {
super(context);
setupView(context);
}
public DotsView(Context context, AttributeSet attrs) {
super(context, attrs);
setupView(context);
}
public DotsView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
setupView(context);
}
private void setupView(Context context) {
setWillNotDraw(false);
paint = new Paint();
paint.setAntiAlias(true);
test();
}
private void test() {
//THIS METHOD IS JUST A TEST THAT CHANGES THE DRAWABLES USED FOR SOME DOTS
for (int i = 2; i < 20; ++i) {
dotsStates[i] = 1;
}
for (int i = 50; i < 55; ++i) {
dotsStates[i] = 2;
}
}
#Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
initBitmaps();
invalidate();
}
#Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
destroyBitmaps();
}
private void initBitmaps() {
testBitmap1 = BitmapFactory.decodeResource(getResources(), R.drawable.test_1);
testBitmap2 = BitmapFactory.decodeResource(getResources(), R.drawable.test_2);
testBitmap3 = BitmapFactory.decodeResource(getResources(), R.drawable.test_3);
dotRect = new RectF(0, 0, dotRadius, dotRadius);
}
private boolean isBitmapValid(Bitmap bitmap) {
return bitmap != null && !bitmap.isRecycled();
}
private void destroyBitmaps() {
if (isBitmapValid(testBitmap1)) {
testBitmap1.recycle();
testBitmap1 = null;
}
if (isBitmapValid(testBitmap2)) {
testBitmap2.recycle();
testBitmap2 = null;
}
if (isBitmapValid(testBitmap3)) {
testBitmap3.recycle();
testBitmap3 = null;
}
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (isBitmapValid(testBitmap1) && isBitmapValid(testBitmap2) && isBitmapValid(testBitmap3)) {
// apply padding to canvas:
final int width = canvas.getWidth();
final int height = canvas.getHeight();
final int squareSide = Math.min(width, height);
canvas.translate(width / 2f, height / 2f); // moving to the center of the View
final float outerRadius = squareSide / 2f;
final float innerRadius = outerRadius - dotRadius;
final float angleFactor = 360f / dots;
for (int i = 0; i < dots; ++i) {
canvas.save(); // creating a "checkpoint"
canvas.rotate(angleFactor * i);
canvas.translate(innerRadius, 0); //moving to the edge of the big circle
canvas.drawBitmap(dotsStates[i] == 0 ?
testBitmap1 :
dotsStates[i] == 1 ?
testBitmap2 : testBitmap3,
null, dotRect, paint);
canvas.restore(); //restoring a "checkpoint"
}
}
}
}
Your approach is super heavyweight. I'd instead recommend making a custom View class, within which you do these things:
Override the onDraw method to draw your circles directly onto the view's Canvas
Implement an onTouchEvent listener, checking the coordinates of the touch against the positions/radii of the circles you created - thus finding the circle (if any) which was tapped
Trigger a custom event like onCircleTapped(View v, int circleId) so that the containing view/activity/fragment can handle the event properly.
You can try this librarytire view
import the barlibrary
create ChartTireView
I hope this will help you.

Making a canvas transparent

In my app I have a surface view with particle explosion animation.I was trying to make that surface view, transparent. After making it transparent, explode animation have a problem. I have attached screenshots of original animation and after making surface view transparent. When setting background color black, its working fine.
MainActivity.java
public class MainActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
LinearLayout surface = (LinearLayout)findViewById(R.id.middleSurface);
surface.addView(new MainGamePanel(this));
}
}
MainGamePanel.java
public class MainGamePanel extends SurfaceView implements
SurfaceHolder.Callback {
private static final String TAG = MainGamePanel.class.getSimpleName();
private static final int EXPLOSION_SIZE = 200;
private MainThread thread;
private Explosion explosion;
// the fps to be displayed
private String avgFps;
public void setAvgFps(String avgFps) {
this.avgFps = avgFps;
}
public MainGamePanel(Context context) {
super(context);
// adding the callback (this) to the surface holder to intercept events
this.setBackgroundColor(Color.TRANSPARENT); //To make canvas transparent
this.setZOrderOnTop(true); //To make canvas transparent
getHolder().setFormat(PixelFormat.TRANSPARENT); //To make canvas transparent!
getHolder().addCallback(this);
// make the GamePanel focusable so it can handle events
setFocusable(true);
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height) {
}
#Override
public void surfaceCreated(SurfaceHolder holder) {
// create the game loop thread
thread = new MainThread(getHolder(), this);
// at this point the surface is created and
// we can safely start the game loop
thread.setRunning(true);
thread.start();
}
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
Log.d(TAG, "Surface is being destroyed");
// tell the thread to shut down and wait for it to finish
// this is a clean shutdown
boolean retry = true;
while (retry) {
try {
thread.setRunning(false);
thread.join();
retry = false;
} catch (InterruptedException e) {
// try again shutting down the thread
}
}
Log.d(TAG, "Thread was shut down cleanly");
}
#Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
// handle touch
// check if explosion is null or if it is still active
if (explosion == null || explosion.getState() == Explosion.STATE_DEAD) {
explosion = new Explosion(EXPLOSION_SIZE, (int)event.getX(), (int)event.getY());
}
}
return true;
}
public void render(Canvas canvas) {
canvas.drawColor(Color.argb(0, 0, 0, 0)); //To make canvas transparent
// render explosions
if (explosion != null) {
explosion.draw(canvas);
}
// display fps
//displayFps(canvas, avgFps);
// display border
/*Paint paint = new Paint();
paint.setColor(Color.GREEN);
canvas.drawLines(new float[]{
0,0, canvas.getWidth()-1,0,
canvas.getWidth()-1,0, canvas.getWidth()-1,canvas.getHeight()-1,
canvas.getWidth()-1,canvas.getHeight()-1, 0,canvas.getHeight()-1,
0,canvas.getHeight()-1, 0,0
}, paint);*/
}
/**
* This is the game update method. It iterates through all the objects
* and calls their update method if they have one or calls specific
* engine's update method.
*/
public void update() {
// update explosions
if (explosion != null && explosion.isAlive()) {
explosion.update(getHolder().getSurfaceFrame());
}
}
private void displayFps(Canvas canvas, String fps) {
if (canvas != null && fps != null) {
Paint paint = new Paint();
paint.setARGB(255, 255, 255, 255);
canvas.drawText(fps, this.getWidth() - 50, 20, paint);
}
}
}
Explosion.java
public class Explosion {
private static final String TAG = Explosion.class.getSimpleName();
public static final int STATE_ALIVE = 0; // at least 1 particle is alive
public static final int STATE_DEAD = 1; // all particles are dead
private Particle[] particles; // particles in the explosion
private int x, y; // the explosion's origin
private float gravity; // the gravity of the explosion (+ upward, - down)
private float wind; // speed of wind on horizontal
private int size; // number of particles
private int state; // whether it's still active or not
public Explosion(int particleNr, int x, int y) {
Log.d(TAG, "Explosion created at " + x + "," + y);
this.state = STATE_ALIVE;
this.particles = new Particle[particleNr];
for (int i = 0; i < this.particles.length; i++) {
Particle p = new Particle(x, y);
this.particles[i] = p;
}
this.size = particleNr;
}
public Particle[] getParticles() {
return particles;
}
public void setParticles(Particle[] particles) {
this.particles = particles;
}
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
public int getY() {
return y;
}
public void setY(int y) {
this.y = y;
}
public float getGravity() {
return gravity;
}
public void setGravity(float gravity) {
this.gravity = gravity;
}
public float getWind() {
return wind;
}
public void setWind(float wind) {
this.wind = wind;
}
public int getSize() {
return size;
}
public void setSize(int size) {
this.size = size;
}
public int getState() {
return state;
}
public void setState(int state) {
this.state = state;
}
// helper methods -------------------------
public boolean isAlive() {
return this.state == STATE_ALIVE;
}
public boolean isDead() {
return this.state == STATE_DEAD;
}
public void update() {
if (this.state != STATE_DEAD) {
boolean isDead = true;
for (int i = 0; i < this.particles.length; i++) {
if (this.particles[i].isAlive()) {
this.particles[i].update();
isDead = false;
}
}
if (isDead)
this.state = STATE_DEAD;
}
}
public void update(Rect container) {
if (this.state != STATE_DEAD) {
boolean isDead = true;
for (int i = 0; i < this.particles.length; i++) {
if (this.particles[i].isAlive()) {
this.particles[i].update(container);
// this.particles[i].update();
isDead = false;
}
}
if (isDead)
this.state = STATE_DEAD;
}
}
public void draw(Canvas canvas) {
for(int i = 0; i < this.particles.length; i++) {
if (this.particles[i].isAlive()) {
this.particles[i].draw(canvas);
}
}
}
}
Particle.java
public class Particle {
public static final int STATE_ALIVE = 0; // particle is alive
public static final int STATE_DEAD = 1; // particle is dead
public static final int DEFAULT_LIFETIME = 200; // play with this
public static final int MAX_DIMENSION = 5; // the maximum width or height
public static final int MAX_SPEED = 10; // maximum speed (per update)
private int state; // particle is alive or dead
private float widht; // width of the particle
private float height; // height of the particle
private float x, y; // horizontal and vertical position
private double xv, yv; // vertical and horizontal velocity
private int age; // current age of the particle
private int lifetime; // particle dies when it reaches this value
private int color; // the color of the particle
private Paint paint; // internal use to avoid instantiation
public int getState() {
return state;
}
public void setState(int state) {
this.state = state;
}
public float getWidht() {
return widht;
}
public void setWidht(float widht) {
this.widht = widht;
}
public float getHeight() {
return height;
}
public void setHeight(float height) {
this.height = height;
}
public float getX() {
return x;
}
public void setX(float x) {
this.x = x;
}
public float getY() {
return y;
}
public void setY(float y) {
this.y = y;
}
public double getXv() {
return xv;
}
public void setXv(double xv) {
this.xv = xv;
}
public double getYv() {
return yv;
}
public void setYv(double yv) {
this.yv = yv;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public int getLifetime() {
return lifetime;
}
public void setLifetime(int lifetime) {
this.lifetime = lifetime;
}
public int getColor() {
return color;
}
public void setColor(int color) {
this.color = color;
}
// helper methods -------------------------
public boolean isAlive() {
return this.state == STATE_ALIVE;
}
public boolean isDead() {
return this.state == STATE_DEAD;
}
public Particle(int x, int y) {
this.x = x;
this.y = y;
this.state = Particle.STATE_ALIVE;
this.widht = rndInt(1, MAX_DIMENSION);
this.height = this.widht;
// this.height = rnd(1, MAX_DIMENSION);
this.lifetime = DEFAULT_LIFETIME;
this.age = 0;
this.xv = (rndDbl(0, MAX_SPEED * 2) - MAX_SPEED);
this.yv = (rndDbl(0, MAX_SPEED * 2) - MAX_SPEED);
// smoothing out the diagonal speed
if (xv * xv + yv * yv > MAX_SPEED * MAX_SPEED) {
xv *= 0.7;
yv *= 0.7;
}
this.color = Color.argb(255, rndInt(0, 255), rndInt(0, 255), rndInt(0, 255));
this.paint = new Paint(this.color);
}
/**
* Resets the particle
* #param x
* #param y
*/
public void reset(float x, float y) {
this.state = Particle.STATE_ALIVE;
this.x = x;
this.y = y;
this.age = 0;
}
// Return an integer that ranges from min inclusive to max inclusive.
static int rndInt(int min, int max) {
return (int) (min + Math.random() * (max - min + 1));
}
static double rndDbl(double min, double max) {
return min + (max - min) * Math.random();
}
public void update() {
if (this.state != STATE_DEAD) {
this.x += this.xv;
this.y += this.yv;
// extract alpha
int a = this.color >>> 24;
a -= 2; // fade by 5
if (a <= 0) { // if reached transparency kill the particle
this.state = STATE_DEAD;
} else {
this.color = (this.color & 0x00ffffff) + (a << 24); // set the new alpha
this.paint.setAlpha(a);
this.age++; // increase the age of the particle
// this.widht *= 1.05;
// this.height *= 1.05;
}
if (this.age >= this.lifetime) { // reached the end if its life
this.state = STATE_DEAD;
}
// http://lab.polygonal.de/2007/05/10/bitwise-gems-fast-integer-math/
//32bit
// var color:uint = 0xff336699;
// var a:uint = color >>> 24;
// var r:uint = color >>> 16 & 0xFF;
// var g:uint = color >>> 8 & 0xFF;
// var b:uint = color & 0xFF;
}
}
public void update(Rect container) {
// update with collision
if (this.isAlive()) {
if (this.x <= container.left || this.x >= container.right - this.widht) {
this.xv *= -1;
}
// Bottom is 480 and top is 0 !!!
if (this.y <= container.top || this.y >= container.bottom - this.height) {
this.yv *= -1;
}
}
update();
}
public void draw(Canvas canvas) {
// paint.setARGB(255, 128, 255, 50);
paint.setColor(this.color);
canvas.drawRect(this.x, this.y, this.x + this.widht, this.y + this.height, paint);
// canvas.drawCircle(x, y, widht, paint);
}
}
activity_main.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#556168"
tools:context="com.offero.MainActivity$PlaceholderFragment" >
<ImageView
android:id="#+id/imageView4"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
android:src="#drawable/bg" />
<ImageView
android:id="#+id/imageView2"
android:layout_width="70dp"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentRight="true"
android:layout_marginRight="5dp"
android:src="#drawable/cloud2" />
<ImageView
android:id="#+id/imageView1"
android:layout_width="70dp"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentLeft="true"
android:layout_marginLeft="5dp"
android:src="#drawable/cloud1" />
<ImageView
android:id="#+id/ImageView02"
android:layout_width="70dp"
android:layout_height="wrap_content"
android:layout_alignLeft="#+id/imageView2"
android:layout_alignParentBottom="true"
android:layout_marginRight="5dp"
android:src="#drawable/cloud2" />
<ImageView
android:id="#+id/ImageView01"
android:layout_width="70dp"
android:layout_height="wrap_content"
android:layout_alignLeft="#+id/imageView1"
android:layout_alignParentBottom="true"
android:layout_marginLeft="5dp"
android:src="#drawable/cloud1" />
<ImageView
android:id="#+id/ImageView03"
android:layout_width="70dp"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:src="#drawable/cloud1" />
<ImageView
android:id="#+id/imageView3"
android:layout_width="wrap_content"
android:layout_height="150dp"
android:layout_centerInParent="true"
android:src="#drawable/rocket" />
<ImageView
android:id="#+id/imageView5"
android:layout_width="wrap_content"
android:layout_height="58dp"
android:layout_alignTop="#+id/imageView3"
android:layout_centerHorizontal="true"
android:src="#drawable/rocketpeice2" />
<ImageView
android:id="#+id/imageView6"
android:layout_width="wrap_content"
android:layout_height="60dp"
android:layout_below="#+id/imageView5"
android:layout_centerHorizontal="true"
android:src="#drawable/rocketpeice1" />
<ImageView
android:id="#+id/imageView_star1"
android:layout_width="wrap_content"
android:layout_height="10dp"
android:layout_alignTop="#+id/imageView6"
android:layout_centerHorizontal="true"
android:src="#drawable/star" />
<ImageView
android:id="#+id/imageView_star2"
android:layout_width="wrap_content"
android:layout_height="15dp"
android:layout_alignTop="#+id/imageView6"
android:layout_centerHorizontal="true"
android:src="#drawable/star" />
<ImageView
android:id="#+id/ImageView_star3"
android:layout_width="wrap_content"
android:layout_height="7dp"
android:layout_alignTop="#+id/imageView6"
android:layout_centerHorizontal="true"
android:src="#drawable/star" />
<ImageView
android:id="#+id/ImageView_star4"
android:layout_width="wrap_content"
android:layout_height="12dp"
android:layout_alignBottom="#+id/ImageView_star3"
android:layout_centerHorizontal="true"
android:src="#drawable/star" />
<ImageView
android:id="#+id/ImageView_star5"
android:layout_width="wrap_content"
android:layout_height="10dp"
android:layout_alignLeft="#+id/imageView_star1"
android:layout_below="#+id/imageView5"
android:src="#drawable/star" />
<ImageView
android:id="#+id/ImageView_star6"
android:layout_width="wrap_content"
android:layout_height="15dp"
android:layout_alignLeft="#+id/imageView_star1"
android:layout_alignTop="#+id/imageView6"
android:src="#drawable/star" />
<ImageView
android:id="#+id/ImageView_star7"
android:layout_width="wrap_content"
android:layout_height="7dp"
android:layout_alignLeft="#+id/imageView_star1"
android:layout_below="#+id/ImageView_star3"
android:src="#drawable/star" />
<ImageView
android:id="#+id/ImageView_star8"
android:layout_width="wrap_content"
android:layout_height="12dp"
android:layout_alignLeft="#+id/imageView_star1"
android:layout_alignTop="#+id/imageView6"
android:src="#drawable/star" />
<LinearLayout
android:id="#+id/middleSurface"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
</LinearLayout>
The below code draws a colour over the canvas.
canvas.drawColor(Color.argb(0, 0, 0, 0)); //To make canvas transparent
I assume you were drawing black over before. Which would draw black, then the new particle positions. Aka clearing the canvas between draws.
Now that you are drawing clear over the canvas between draws, you are literally doing nothing. As drawing nothing has no effect.
What you need to actually do is clear the canvas.
you need to use the code:
canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
This draws Transparent, but also tells it not to merge with existing particles, but to actually clear them :)
Ref: https://stackoverflow.com/a/10882301/940834

Android Gallery zoom in/out

Hi I am using the Gallery widget to show images downloaded from the internet.
to show several images and I would like to have a gradual zoom while people slide up and down on the screen. I know how to implement the touch event the only thing I don't know how to make the whole gallery view grow gradually. I don't want to zoom in on one image I want the whole gallery to zoom in/out gradually.
EDIT3: I manage to zoom the visible part of the gallery but the problem is I need to find a way for the gallery to find out about it and update it's other children too.
What happens is if 3 images are visible then you start zooming and the gallery does get smaller, so do the images but what I would like in this case is more images to be visible but I don't know how to reach this desired effect. Here's the entire code:
public class Gallery1 extends Activity implements OnTouchListener {
private static final String TAG = "GalleryTest";
private float zoom=0.0f;
// Remember some things for zooming
PointF start = new PointF();
PointF mid = new PointF();
Gallery g;
LinearLayout layout2;
private ImageAdapter ad;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.gallery_1);
layout2=(LinearLayout) findViewById(R.id.layout2);
// Reference the Gallery view
g = (Gallery) findViewById(R.id.gallery);
// Set the adapter to our custom adapter (below)
ad=new ImageAdapter(this);
g.setAdapter(ad);
layout2.setOnTouchListener(this);
}
public void zoomList(boolean increase) {
Log.i(TAG, "startig animation");
AnimatorSet set = new AnimatorSet();
set.playTogether(
ObjectAnimator.ofFloat(g, "scaleX", zoom),
ObjectAnimator.ofFloat(g, "scaleY", zoom)
);
set.addListener(new AnimatorListener() {
#Override
public void onAnimationStart(Animator animation) {
}
#Override
public void onAnimationRepeat(Animator animation) {
// TODO Auto-generated method stub
}
#Override
public void onAnimationEnd(Animator animation) {
}
#Override
public void onAnimationCancel(Animator animation) {
// TODO Auto-generated method stub
}
});
set.setDuration(100).start();
}
public class ImageAdapter extends BaseAdapter {
private static final int ITEM_WIDTH = 136;
private static final int ITEM_HEIGHT = 88;
private final int mGalleryItemBackground;
private final Context mContext;
private final Integer[] mImageIds = {
R.drawable.gallery_photo_1,
R.drawable.gallery_photo_2,
R.drawable.gallery_photo_3,
R.drawable.gallery_photo_4,
R.drawable.gallery_photo_5,
R.drawable.gallery_photo_6,
R.drawable.gallery_photo_7,
R.drawable.gallery_photo_8
};
private final float mDensity;
public ImageAdapter(Context c) {
mContext = c;
// See res/values/attrs.xml for the <declare-styleable> that defines
// Gallery1.
TypedArray a = obtainStyledAttributes(R.styleable.Gallery1);
mGalleryItemBackground = a.getResourceId(
R.styleable.Gallery1_android_galleryItemBackground, 1);
a.recycle();
mDensity = c.getResources().getDisplayMetrics().density;
}
public int getCount() {
return mImageIds.length;
}
public Object getItem(int position) {
return position;
}
public long getItemId(int position) {
return position;
}
public View getView(int position, View convertView, ViewGroup parent) {
ImageView imageView;
if (convertView == null) {
convertView = new ImageView(mContext);
imageView = (ImageView) convertView;
imageView.setScaleType(ImageView.ScaleType.FIT_XY);
imageView.setLayoutParams(new Gallery.LayoutParams(
(int) (ITEM_WIDTH * mDensity + 0.5f),
(int) (ITEM_HEIGHT * mDensity + 0.5f)));
} else {
imageView = (ImageView) convertView;
}
imageView.setImageResource(mImageIds[position]);
return imageView;
}
}
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_MOVE
&& event.getPointerCount() > 1) {
midPoint(mid, event);
if(mid.y > start.y){
Log.i(TAG, "Going down (Math.abs(mid.y - start.y)= "+(Math.abs(mid.y - start.y))+" and zoom="+zoom); // going down so increase
if ((Math.abs(mid.y - start.y) > 10) && (zoom<2.5f)){
zoom=zoom+0.1f;
midPoint(start, event);
zoomList(true);
}
return true;
}else if(mid.y < start.y){
Log.i(TAG, "Going up (Math.abs(mid.y - start.y)= "+(Math.abs(mid.y - start.y))+" and zoom="+zoom); //smaller
if ((Math.abs(mid.y - start.y) > 10) &&(zoom>0.1)){
midPoint(start, event);
zoom=zoom-0.1f;
zoomList(false);
}
return true;
}
}
else if (event.getAction() == MotionEvent.ACTION_POINTER_DOWN) {
Log.e(TAG, "Pointer went down: " + event.getPointerCount());
return true;
}
else if (event.getAction() == MotionEvent.ACTION_UP) {
Log.i(TAG, "Pointer going up");
return true;
}
else if (event.getAction() == MotionEvent.ACTION_DOWN) {
Log.i(TAG, "Pointer going down");
start.set(event.getX(), event.getY());
return true;
}
return false;
// indicate event was handled or not
}
private void midPoint(PointF point, MotionEvent event) {
float x = event.getX(0) + event.getX(1);
float y = event.getY(0) + event.getY(1);
point.set(x / 2, y / 2);
}
I realise I will probably have to extend the Gallery or even another View group or create my own class but I don't know where to start: which method use the one responsible for scaling...
EDIT4: I don't know if he question is clear enough. Here is an example of states:
State one: initial state, we have 3 images in view
State 2: we detect vertical touches going up with 2 fingers = we have to zoom out
state 3: we start zooming = animation on the gallery or on the children???
state 4: gallery detects that it's 3 children are smaller
state 5: gallery adds 1 /more children according to the new available space
LAST UPDATE:
Thanks to all that have posted but I have finally reached a conclusion and that is to not use Gallery at all:
1. It's deprecated
2. It's not customizable enough for my case
If you want to animate several images at once you may want to consider using OpenGl, I am using libgdx library:
https://github.com/libgdx/libgdx
The following ScalingGallery implementation might be of help.
This gallery subclass overrides the getChildStaticTransformation(View child, Transformation t) method in which the scaling is performed. You can further customize the scaling parameters to fit your own needs.
Please note the ScalingGalleryItemLayout.java class. This is necessary because after you have performed the scaling operationg on the child views, their hit boxes are no longer valid so they must be updated from with the getChildStaticTransformation(View child, Transformation t) method.
This is done by wrapping each gallery item in a ScalingGalleryItemLayout which extends a LinearLayout. Again, you can customize this to fit your own needs if a LinearLayout does not meet your needs for layout out your gallery items.
File : /src/com/example/ScalingGallery.java
/**
* A Customized Gallery component which alters the size and position of its items based on their position in the Gallery.
*/
public class ScalingGallery extends Gallery {
public static final int ITEM_SPACING = -20;
private static final float SIZE_SCALE_MULTIPLIER = 0.25f;
private static final float ALPHA_SCALE_MULTIPLIER = 0.5f;
private static final float X_OFFSET = 20.0f;
/**
* Implemented by child view to adjust the boundaries after it has been matrix transformed.
*/
public interface SetHitRectInterface {
public void setHitRect(RectF newRect);
}
/**
* #param context
* Context that this Gallery will be used in.
* #param attrs
* Attributes for this Gallery (via either xml or in-code)
*/
public ScalingGallery(Context context, AttributeSet attrs) {
super(context, attrs);
setStaticTransformationsEnabled(true);
setChildrenDrawingOrderEnabled(true);
}
/**
* {#inheritDoc}
*
* #see #setStaticTransformationsEnabled(boolean)
*
* This is where the scaling happens.
*/
protected boolean getChildStaticTransformation(View child, Transformation t) {
child.invalidate();
t.clear();
t.setTransformationType(Transformation.TYPE_BOTH);
// Position of the child in the Gallery (... +2 +1 0 -1 -2 ... 0 being the middle)
final int childPosition = getSelectedItemPosition() - getPositionForView(child);
final int childPositionAbs = (int) Math.abs(childPosition);
final float left = child.getLeft();
final float top = child.getTop();
final float right = child.getRight();
final float bottom = child.getBottom();
Matrix matrix = t.getMatrix();
RectF modifiedHitBox = new RectF();
// Change alpha, scale and translate non-middle child views.
if (childPosition != 0) {
final int height = child.getMeasuredHeight();
final int width = child.getMeasuredWidth();
// Scale the size.
float scaledSize = 1.0f - (childPositionAbs * SIZE_SCALE_MULTIPLIER);
if (scaledSize < 0) {
scaledSize = 0;
}
matrix.setScale(scaledSize, scaledSize);
float moveX = 0;
float moveY = 0;
// Moving from right to left -- linear move since the scaling is done with respect to top-left corner of the view.
if (childPosition < 0) {
moveX = ((childPositionAbs - 1) * SIZE_SCALE_MULTIPLIER * width) + X_OFFSET;
moveX *= -1;
} else { // Moving from left to right -- sum of the previous positions' x displacements.
// X(n) = X(0) + X(1) + X(2) + ... + X(n-1)
for (int i = childPositionAbs; i > 0; i--) {
moveX += (i * SIZE_SCALE_MULTIPLIER * width);
}
moveX += X_OFFSET;
}
// Moving down y-axis is linear.
moveY = ((childPositionAbs * SIZE_SCALE_MULTIPLIER * height) / 2);
matrix.postTranslate(moveX, moveY);
// Scale alpha value.
final float alpha = (1.0f / childPositionAbs) * ALPHA_SCALE_MULTIPLIER;
t.setAlpha(alpha);
// Calculate new hit box. Since we moved the child, the hitbox is no longer lined up with the new child position.
final float newLeft = left + moveX;
final float newTop = top + moveY;
final float newRight = newLeft + (width * scaledSize);
final float newBottom = newTop + (height * scaledSize);
modifiedHitBox = new RectF(newLeft, newTop, newRight, newBottom);
} else {
modifiedHitBox = new RectF(left, top, right, bottom);
}
// update child hit box so you can tap within the child's boundary
((SetHitRectInterface) child).setHitRect(modifiedHitBox);
return true;
}
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
// Helps to smooth out jittering during scrolling.
// read more - http://www.unwesen.de/2011/04/17/android-jittery-scrolling-gallery/
final int viewsOnScreen = getLastVisiblePosition() - getFirstVisiblePosition();
if (viewsOnScreen <= 0) {
super.onLayout(changed, l, t, r, b);
}
}
private int mLastDrawnPosition;
#Override
protected int getChildDrawingOrder(int childCount, int i) {
//Reset the last position variable every time we are starting a new drawing loop
if (i == 0) {
mLastDrawnPosition = 0;
}
final int centerPosition = getSelectedItemPosition() - getFirstVisiblePosition();
if (i == childCount - 1) {
return centerPosition;
} else if (i >= centerPosition) {
mLastDrawnPosition++;
return childCount - mLastDrawnPosition;
} else {
return i;
}
}
}
File : /src/com/example/ScalingGalleryItemLayout.java
public class ScalingGalleryItemLayout extends LinearLayout implements SetHitRectInterface {
public ScalingGalleryItemLayout(Context context) {
super(context);
}
public ScalingGalleryItemLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ScalingGalleryItemLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
private Rect mTransformedRect;
#Override
public void setHitRect(RectF newRect) {
if (newRect == null) {
return;
}
if (mTransformedRect == null) {
mTransformedRect = new Rect();
}
newRect.round(mTransformedRect);
}
#Override
public void getHitRect(Rect outRect) {
if (mTransformedRect == null) {
super.getHitRect(outRect);
} else {
outRect.set(mTransformedRect);
}
}
}
File : /res/layout/ScaledGalleryItemLayout.xml
<?xml version="1.0" encoding="utf-8"?>
<com.example.ScalingGalleryItemLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/gallery_item_layout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center"
android:orientation="vertical"
android:padding="5dp" >
<ImageView
android:id="#+id/gallery_item_image"
android:layout_width="360px"
android:layout_height="210px"
android:layout_gravity="center"
android:antialias="true"
android:background="#drawable/gallery_item_button_selector"
android:cropToPadding="true"
android:padding="35dp"
android:scaleType="centerInside" />
<TextView
android:id="#+id/gallery_item_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:textColor="#drawable/white"
android:textSize="30sp" />
</com.example.ScalingGalleryItemLayout>
To keep the state of the animation after it is done, just do this on your animation:
youranim.setFillAfter(true);
Edit :
In my project, I use this method and i think, it's help you :
http://developer.sonymobile.com/wp/2011/04/12/how-to-take-advantage-of-the-pinch-to-zoom-feature-in-your-xperia%E2%84%A2-10-apps-part-1/
U can do Image Zoom pinch option for gallery also.
by using below code lines:
you can download the example.
https://github.com/alvinsj/android-image-gallery/downloads
I hope this example will help to u..if u have any queries ask me.....
This is solution
integrate gallery component in android with gesture-image library
gesture-imageView
And here is full sample code
SampleCode

Fast Scroll display problem with ListAdapter and SectionIndexer

I have a list of events which are seperated by month and year (Jun 2010, Jul 2010 etc.). I have enabled fast scrolling because the list is really long. I've also implemented SectionIndexer so that people can see what month and year they are currently viewing when scrolling down the list of events at speed.
I don't have any problem with the implementation, just how the information is shown. Fast scrolling with SectionIndexer seems to only really be able to support a label with a single letter. If the list was alphabetised this would be perfect, however I want it to display a bit more text.
If you look at the screenshot bellow you'll see the problem I'm having.
(source: matto1990.com)
What I want to know is: is it possible to change how the text in the centre of the screen is displayed. Can I change it somehow to make it look right (with the background covering all of the text).
Thanks in advance. If you need any clarification, or code just ask.
EDIT: Full sample code for this solution available here.
I had this same problem - I needed to display full text in the overlay rectangle rather than just a single character. I managed to solve it using the following code as an example: http://code.google.com/p/apps-for-android/source/browse/trunk/RingsExtended/src/com/example/android/rings_extended/FastScrollView.java
The author said that this was copied from the Contacts app, which apparently uses its own implementation rather than just setting fastScrollEnabled="true" on the ListView. I altered it a little bit so that you can customize the overlay rectangle width, overlay rectangle height, overlay text size, and scroll thumb width.
For the record, the final result looks like this: http://nolanwlawson.files.wordpress.com/2011/03/pokedroid_1.png
All you need to do is add these values to your res/values/attrs.xml:
<declare-styleable name="CustomFastScrollView">
<attr name="overlayWidth" format="dimension"/>
<attr name="overlayHeight" format="dimension"/>
<attr name="overlayTextSize" format="dimension"/>
<attr name="overlayScrollThumbWidth" format="dimension"/>
</declare-styleable>
And then use this CustomFastScrollView instead of the one in the link:
public class CustomFastScrollView extends FrameLayout
implements OnScrollListener, OnHierarchyChangeListener {
private Drawable mCurrentThumb;
private Drawable mOverlayDrawable;
private int mThumbH;
private int mThumbW;
private int mThumbY;
private RectF mOverlayPos;
// custom values I defined
private int mOverlayWidth;
private int mOverlayHeight;
private float mOverlayTextSize;
private int mOverlayScrollThumbWidth;
private boolean mDragging;
private ListView mList;
private boolean mScrollCompleted;
private boolean mThumbVisible;
private int mVisibleItem;
private Paint mPaint;
private int mListOffset;
private Object [] mSections;
private String mSectionText;
private boolean mDrawOverlay;
private ScrollFade mScrollFade;
private Handler mHandler = new Handler();
private BaseAdapter mListAdapter;
private boolean mChangedBounds;
public static interface SectionIndexer {
Object[] getSections();
int getPositionForSection(int section);
int getSectionForPosition(int position);
}
public CustomFastScrollView(Context context) {
super(context);
init(context, null);
}
public CustomFastScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
public CustomFastScrollView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context, attrs);
}
private void useThumbDrawable(Drawable drawable) {
mCurrentThumb = drawable;
mThumbW = mOverlayScrollThumbWidth;//mCurrentThumb.getIntrinsicWidth();
mThumbH = mCurrentThumb.getIntrinsicHeight();
mChangedBounds = true;
}
private void init(Context context, AttributeSet attrs) {
// set all attributes from xml
if (attrs != null) {
TypedArray typedArray = context.obtainStyledAttributes(attrs,
R.styleable.CustomFastScrollView);
mOverlayHeight = typedArray.getDimensionPixelSize(
R.styleable.CustomFastScrollView_overlayHeight, 0);
mOverlayWidth = typedArray.getDimensionPixelSize(
R.styleable.CustomFastScrollView_overlayWidth, 0);
mOverlayTextSize = typedArray.getDimensionPixelSize(
R.styleable.CustomFastScrollView_overlayTextSize, 0);
mOverlayScrollThumbWidth = typedArray.getDimensionPixelSize(
R.styleable.CustomFastScrollView_overlayScrollThumbWidth, 0);
}
// Get both the scrollbar states drawables
final Resources res = context.getResources();
Drawable thumbDrawable = res.getDrawable(R.drawable.scrollbar_handle_accelerated_anim2);
useThumbDrawable(thumbDrawable);
mOverlayDrawable = res.getDrawable(android.R.drawable.alert_dark_frame);
mScrollCompleted = true;
setWillNotDraw(false);
// Need to know when the ListView is added
setOnHierarchyChangeListener(this);
mOverlayPos = new RectF();
mScrollFade = new ScrollFade();
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setTextAlign(Paint.Align.CENTER);
mPaint.setTextSize(mOverlayTextSize);
mPaint.setColor(0xFFFFFFFF);
mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
}
private void removeThumb() {
mThumbVisible = false;
// Draw one last time to remove thumb
invalidate();
}
#Override
public void draw(Canvas canvas) {
super.draw(canvas);
if (!mThumbVisible) {
// No need to draw the rest
return;
}
final int y = mThumbY;
final int viewWidth = getWidth();
final CustomFastScrollView.ScrollFade scrollFade = mScrollFade;
int alpha = -1;
if (scrollFade.mStarted) {
alpha = scrollFade.getAlpha();
if (alpha < ScrollFade.ALPHA_MAX / 2) {
mCurrentThumb.setAlpha(alpha * 2);
}
int left = viewWidth - (mThumbW * alpha) / ScrollFade.ALPHA_MAX;
mCurrentThumb.setBounds(left, 0, viewWidth, mThumbH);
mChangedBounds = true;
}
canvas.translate(0, y);
mCurrentThumb.draw(canvas);
canvas.translate(0, -y);
// If user is dragging the scroll bar, draw the alphabet overlay
if (mDragging && mDrawOverlay) {
mOverlayDrawable.draw(canvas);
final Paint paint = mPaint;
float descent = paint.descent();
final RectF rectF = mOverlayPos;
canvas.drawText(mSectionText, (int) (rectF.left + rectF.right) / 2,
(int) (rectF.bottom + rectF.top) / 2 + descent, paint);
} else if (alpha == 0) {
scrollFade.mStarted = false;
removeThumb();
} else {
invalidate(viewWidth - mThumbW, y, viewWidth, y + mThumbH);
}
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
if (mCurrentThumb != null) {
mCurrentThumb.setBounds(w - mThumbW, 0, w, mThumbH);
}
final RectF pos = mOverlayPos;
pos.left = (w - mOverlayWidth) / 2;
pos.right = pos.left + mOverlayWidth;
pos.top = h / 10; // 10% from top
pos.bottom = pos.top + mOverlayHeight;
mOverlayDrawable.setBounds((int) pos.left, (int) pos.top,
(int) pos.right, (int) pos.bottom);
}
public void onScrollStateChanged(AbsListView view, int scrollState) {
}
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
int totalItemCount) {
if (totalItemCount - visibleItemCount > 0 && !mDragging) {
mThumbY = ((getHeight() - mThumbH) * firstVisibleItem) / (totalItemCount - visibleItemCount);
if (mChangedBounds) {
final int viewWidth = getWidth();
mCurrentThumb.setBounds(viewWidth - mThumbW, 0, viewWidth, mThumbH);
mChangedBounds = false;
}
}
mScrollCompleted = true;
if (firstVisibleItem == mVisibleItem) {
return;
}
mVisibleItem = firstVisibleItem;
if (!mThumbVisible || mScrollFade.mStarted) {
mThumbVisible = true;
mCurrentThumb.setAlpha(ScrollFade.ALPHA_MAX);
}
mHandler.removeCallbacks(mScrollFade);
mScrollFade.mStarted = false;
if (!mDragging) {
mHandler.postDelayed(mScrollFade, 1500);
}
}
private void getSections() {
Adapter adapter = mList.getAdapter();
if (adapter instanceof HeaderViewListAdapter) {
mListOffset = ((HeaderViewListAdapter)adapter).getHeadersCount();
adapter = ((HeaderViewListAdapter)adapter).getWrappedAdapter();
}
if (adapter instanceof SectionIndexer) {
mListAdapter = (BaseAdapter) adapter;
mSections = ((SectionIndexer) mListAdapter).getSections();
}
}
public void onChildViewAdded(View parent, View child) {
if (child instanceof ListView) {
mList = (ListView)child;
mList.setOnScrollListener(this);
getSections();
}
}
public void onChildViewRemoved(View parent, View child) {
if (child == mList) {
mList = null;
mListAdapter = null;
mSections = null;
}
}
#Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (mThumbVisible && ev.getAction() == MotionEvent.ACTION_DOWN) {
if (ev.getX() > getWidth() - mThumbW && ev.getY() >= mThumbY &&
ev.getY() <= mThumbY + mThumbH) {
mDragging = true;
return true;
}
}
return false;
}
private void scrollTo(float position) {
int count = mList.getCount();
mScrollCompleted = false;
final Object[] sections = mSections;
int sectionIndex;
if (sections != null && sections.length > 1) {
final int nSections = sections.length;
int section = (int) (position * nSections);
if (section >= nSections) {
section = nSections - 1;
}
sectionIndex = section;
final SectionIndexer baseAdapter = (SectionIndexer) mListAdapter;
int index = baseAdapter.getPositionForSection(section);
// Given the expected section and index, the following code will
// try to account for missing sections (no names starting with..)
// It will compute the scroll space of surrounding empty sections
// and interpolate the currently visible letter's range across the
// available space, so that there is always some list movement while
// the user moves the thumb.
int nextIndex = count;
int prevIndex = index;
int prevSection = section;
int nextSection = section + 1;
// Assume the next section is unique
if (section < nSections - 1) {
nextIndex = baseAdapter.getPositionForSection(section + 1);
}
// Find the previous index if we're slicing the previous section
if (nextIndex == index) {
// Non-existent letter
while (section > 0) {
section--;
prevIndex = baseAdapter.getPositionForSection(section);
if (prevIndex != index) {
prevSection = section;
sectionIndex = section;
break;
}
}
}
// Find the next index, in case the assumed next index is not
// unique. For instance, if there is no P, then request for P's
// position actually returns Q's. So we need to look ahead to make
// sure that there is really a Q at Q's position. If not, move
// further down...
int nextNextSection = nextSection + 1;
while (nextNextSection < nSections &&
baseAdapter.getPositionForSection(nextNextSection) == nextIndex) {
nextNextSection++;
nextSection++;
}
// Compute the beginning and ending scroll range percentage of the
// currently visible letter. This could be equal to or greater than
// (1 / nSections).
float fPrev = (float) prevSection / nSections;
float fNext = (float) nextSection / nSections;
index = prevIndex + (int) ((nextIndex - prevIndex) * (position - fPrev)
/ (fNext - fPrev));
// Don't overflow
if (index > count - 1) index = count - 1;
mList.setSelectionFromTop(index + mListOffset, 0);
} else {
int index = (int) (position * count);
mList.setSelectionFromTop(index + mListOffset, 0);
sectionIndex = -1;
}
if (sectionIndex >= 0) {
String text = mSectionText = sections[sectionIndex].toString();
mDrawOverlay = (text.length() != 1 || text.charAt(0) != ' ') &&
sectionIndex < sections.length;
} else {
mDrawOverlay = false;
}
}
private void cancelFling() {
// Cancel the list fling
MotionEvent cancelFling = MotionEvent.obtain(0, 0, MotionEvent.ACTION_CANCEL, 0, 0, 0);
mList.onTouchEvent(cancelFling);
cancelFling.recycle();
}
#Override
public boolean onTouchEvent(MotionEvent me) {
if (me.getAction() == MotionEvent.ACTION_DOWN) {
if (me.getX() > getWidth() - mThumbW
&& me.getY() >= mThumbY
&& me.getY() <= mThumbY + mThumbH) {
mDragging = true;
if (mListAdapter == null && mList != null) {
getSections();
}
cancelFling();
return true;
}
} else if (me.getAction() == MotionEvent.ACTION_UP) {
if (mDragging) {
mDragging = false;
final Handler handler = mHandler;
handler.removeCallbacks(mScrollFade);
handler.postDelayed(mScrollFade, 1000);
return true;
}
} else if (me.getAction() == MotionEvent.ACTION_MOVE) {
if (mDragging) {
final int viewHeight = getHeight();
mThumbY = (int) me.getY() - mThumbH + 10;
if (mThumbY < 0) {
mThumbY = 0;
} else if (mThumbY + mThumbH > viewHeight) {
mThumbY = viewHeight - mThumbH;
}
// If the previous scrollTo is still pending
if (mScrollCompleted) {
scrollTo((float) mThumbY / (viewHeight - mThumbH));
}
return true;
}
}
return super.onTouchEvent(me);
}
public class ScrollFade implements Runnable {
long mStartTime;
long mFadeDuration;
boolean mStarted;
static final int ALPHA_MAX = 200;
static final long FADE_DURATION = 200;
void startFade() {
mFadeDuration = FADE_DURATION;
mStartTime = SystemClock.uptimeMillis();
mStarted = true;
}
int getAlpha() {
if (!mStarted) {
return ALPHA_MAX;
}
int alpha;
long now = SystemClock.uptimeMillis();
if (now > mStartTime + mFadeDuration) {
alpha = 0;
} else {
alpha = (int) (ALPHA_MAX - ((now - mStartTime) * ALPHA_MAX) / mFadeDuration);
}
return alpha;
}
public void run() {
if (!mStarted) {
startFade();
invalidate();
}
if (getAlpha() > 0) {
final int y = mThumbY;
final int viewWidth = getWidth();
invalidate(viewWidth - mThumbW, y, viewWidth, y + mThumbH);
} else {
mStarted = false;
removeThumb();
}
}
}
}
You can also tweak the translucency of the scroll thumb using ALPHA_MAX.
Then put something like this in your layout xml file:
<com.myapp.CustomFastScrollView android:layout_width="wrap_content"
android:layout_height="fill_parent"
myapp:overlayWidth="175dp" myapp:overlayHeight="110dp" myapp:overlayTextSize="36dp"
myapp:overlayScrollThumbWidth="60dp" android:id="#+id/fast_scroll_view">
<ListView android:id="#android:id/list" android:layout_width="wrap_content"
android:layout_height="fill_parent"/>
<TextView android:id="#android:id/empty"
android:layout_width="wrap_content" android:layout_height="wrap_content"
android:text="" />
</com.myapp.CustomFastScrollView>
Don't forget to declare your attributes in that layout xml file as well:
... xmlns:myapp= "http://schemas.android.com/apk/res/com.myapp" ...
You'll also need to grab the R.drawable.scrollbar_handle_accelerated_anim2 drawables from that Android source code. The link above only contains the mdpi one.
The FastScroller widget is responsible for drawing the overlay. You should probably take a look at its source:
https://android.googlesource.com/platform/frameworks/base/+/gingerbread-release/core/java/android/widget/FastScroller.java
Search for comment:
// If user is dragging the scroll bar, draw the alphabet overlay

Categories

Resources