Related
I am using MPAndroidChart library and showing a line chart. There is methods to show circles on all values but I want to show circle ONLY on Last value. How can I do that?
You can set setScatterShape to CIRCLE
ScatterDataSet set2 = new ScatterDataSet(values2, "DS 2");
set2.setScatterShape(ScatterChart.ScatterShape.CIRCLE);
I have similar needs, draw the circle if and only if the selected position is highlighted.
MyLineChart.java
public class MyLineChart extends LineChart {
public MyLineChart(Context context) {
super(context);
}
public MyLineChart(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MyLineChart(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
#Override
protected void init() {
super.init();
mRenderer = new MyLineChartRenderer(this, mAnimator, mViewPortHandler);
}
public void setHighIndex(int index) {
((MyLineChartRenderer) mRenderer).setHighIndex(index);
invalidate();
}
}
MyLineChartRenderer.java
public class MyLineChartRenderer extends LineChartRenderer {
public MyLineChartRenderer(LineDataProvider chart, ChartAnimator animator, ViewPortHandler viewPortHandler) {
super(chart, animator, viewPortHandler);
}
private float[] mCirclesBuffer = new float[2];
/**
* cache for the circle bitmaps of all datasets
*/
private HashMap<IDataSet, DataSetImageCache> mImageCaches = new HashMap<>();
#Override
protected void drawCircles(Canvas c) {
mRenderPaint.setStyle(Paint.Style.FILL);
float phaseY = mAnimator.getPhaseY();
mCirclesBuffer[0] = 0;
mCirclesBuffer[1] = 0;
List<ILineDataSet> dataSets = mChart.getLineData().getDataSets();
for (int i = 0; i < dataSets.size(); i++) {
ILineDataSet dataSet = dataSets.get(i);
if (!dataSet.isVisible() || (!dataSet.isDrawCirclesEnabled() && -1 == highIndex) ||
dataSet.getEntryCount() == 0)
continue;
mCirclePaintInner.setColor(dataSet.getCircleHoleColor());
Transformer trans = mChart.getTransformer(dataSet.getAxisDependency());
mXBounds.set(mChart, dataSet);
float circleRadius = dataSet.getCircleRadius();
float circleHoleRadius = dataSet.getCircleHoleRadius();
boolean drawCircleHole = dataSet.isDrawCircleHoleEnabled() &&
circleHoleRadius < circleRadius &&
circleHoleRadius > 0.f;
boolean drawTransparentCircleHole = drawCircleHole &&
dataSet.getCircleHoleColor() == ColorTemplate.COLOR_NONE;
DataSetImageCache imageCache;
if (mImageCaches.containsKey(dataSet)) {
imageCache = mImageCaches.get(dataSet);
} else {
imageCache = new DataSetImageCache();
mImageCaches.put(dataSet, imageCache);
}
boolean changeRequired = imageCache.init(dataSet);
// only fill the cache with new bitmaps if a change is required
if (changeRequired) {
imageCache.fill(dataSet, drawCircleHole, drawTransparentCircleHole);
}
int boundsRangeCount = mXBounds.range + mXBounds.min;
//draw circle on line
for (int j = mXBounds.min; j <= boundsRangeCount; j++) {
Entry e = dataSet.getEntryForIndex(j);
if (e == null) break;
//Draw the circle if and only if the selected position is highlighted
if (j != highIndex)
continue;
mCirclesBuffer[0] = e.getX();
mCirclesBuffer[1] = e.getY() * phaseY;
trans.pointValuesToPixel(mCirclesBuffer);
if (!mViewPortHandler.isInBoundsRight(mCirclesBuffer[0]))
break;
if (!mViewPortHandler.isInBoundsLeft(mCirclesBuffer[0]) ||
!mViewPortHandler.isInBoundsY(mCirclesBuffer[1]))
continue;
Bitmap circleBitmap = imageCache.getBitmap(j);
if (circleBitmap != null) {
c.drawBitmap(circleBitmap, mCirclesBuffer[0] - circleRadius, mCirclesBuffer[1] - circleRadius, null);
}
}
}
}
private int highIndex = -1;
public void setHighIndex(int index) {
highIndex = index;
}
private class DataSetImageCache {
private Path mCirclePathBuffer = new Path();
private Bitmap[] circleBitmaps;
/**
* Sets up the cache, returns true if a change of cache was required.
*
* #param set
* #return
*/
protected boolean init(ILineDataSet set) {
int size = set.getCircleColorCount();
boolean changeRequired = false;
if (circleBitmaps == null) {
circleBitmaps = new Bitmap[size];
changeRequired = true;
} else if (circleBitmaps.length != size) {
circleBitmaps = new Bitmap[size];
changeRequired = true;
}
return changeRequired;
}
/**
* Fills the cache with bitmaps for the given dataset.
*
* #param set
* #param drawCircleHole
* #param drawTransparentCircleHole
*/
protected void fill(ILineDataSet set, boolean drawCircleHole, boolean drawTransparentCircleHole) {
int colorCount = set.getCircleColorCount();
float circleRadius = set.getCircleRadius();
float circleHoleRadius = set.getCircleHoleRadius();
for (int i = 0; i < colorCount; i++) {
Bitmap.Config conf = Bitmap.Config.ARGB_4444;
Bitmap circleBitmap = Bitmap.createBitmap((int) (circleRadius * 2.1), (int) (circleRadius * 2.1), conf);
Canvas canvas = new Canvas(circleBitmap);
circleBitmaps[i] = circleBitmap;
mRenderPaint.setColor(set.getCircleColor(i));
if (drawTransparentCircleHole) {
// Begin path for circle with hole
mCirclePathBuffer.reset();
mCirclePathBuffer.addCircle(
circleRadius,
circleRadius,
circleRadius,
Path.Direction.CW);
// Cut hole in path
mCirclePathBuffer.addCircle(
circleRadius,
circleRadius,
circleHoleRadius,
Path.Direction.CCW);
// Fill in-between
canvas.drawPath(mCirclePathBuffer, mRenderPaint);
} else {
canvas.drawCircle(
circleRadius,
circleRadius,
circleRadius,
mRenderPaint);
if (drawCircleHole) {
canvas.drawCircle(
circleRadius,
circleRadius,
circleHoleRadius,
mCirclePaintInner);
}
}
}
}
/**
* Returns the cached Bitmap at the given index.
*
* #param index
* #return
*/
protected Bitmap getBitmap(int index) {
return circleBitmaps[index % circleBitmaps.length];
}
}
}
I tried to make a single ball bouncing to dynamic ball bouncing . Eg: here the number of circles is 50.
But I am getting error while trying to make the circles dynamic (Model) .How do I make it work and make the model/circle dynamic.In this case 50 circles ? I really appreciate any help. Thanks in Advance.
package com.stuffthathappens.games;
import static android.hardware.SensorManager.DATA_X;
import static android.hardware.SensorManager.DATA_Y;
import static android.hardware.SensorManager.SENSOR_ACCELEROMETER;
import static android.hardware.SensorManager.SENSOR_DELAY_GAME;
import java.util.concurrent.TimeUnit;
import android.app.Activity;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.hardware.SensorListener;
import android.hardware.SensorManager;
import android.os.Bundle;
import android.os.Vibrator;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.SurfaceHolder.Callback;
/**
* This activity shows a ball that bounces around. The phone's
* accelerometer acts as gravity on the ball. When the ball hits
* the edge, it bounces back and triggers the phone vibrator.
*/
#SuppressWarnings("deprecation")
public class BouncingBallActivity extends Activity implements Callback, SensorListener {
private static final int BALL_RADIUS =20;
private SurfaceView surface;
private SurfaceHolder holder;
private GameLoop gameLoop;
private Paint backgroundPaint;
private Paint ballPaint;
private SensorManager sensorMgr;
private long lastSensorUpdate = -1;
private Paint ballPaintyellow;
private BouncingBallModel[] model;
int Totalcircles=50;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.bouncing_ball);
for (int i = 0; i < Totalcircles; i++) {
model[i] = new BouncingBallModel(BALL_RADIUS);
}
surface = (SurfaceView) findViewById(R.id.bouncing_ball_surface);
holder = surface.getHolder();
surface.getHolder().addCallback(this);
backgroundPaint = new Paint();
backgroundPaint.setColor(Color.WHITE);
ballPaint = new Paint();
ballPaint.setColor(Color.BLUE);
ballPaint.setAntiAlias(true);
ballPaintyellow = new Paint();
ballPaintyellow.setColor(Color.YELLOW);
ballPaintyellow.setAntiAlias(true);
}
#Override
protected void onPause() {
super.onPause();
for (int i = 0; i < Totalcircles; i++) {
model[i].setVibrator(null);
}
sensorMgr.unregisterListener(this, SENSOR_ACCELEROMETER);
sensorMgr = null;
for (int i = 0; i < Totalcircles; i++) {
model[i].setAccel(0, 0);
}
}
#Override
protected void onResume() {
super.onResume();
sensorMgr = (SensorManager) getSystemService(SENSOR_SERVICE);
boolean accelSupported = sensorMgr.registerListener(this,
SENSOR_ACCELEROMETER,
SENSOR_DELAY_GAME);
if (!accelSupported) {
// on accelerometer on this device
sensorMgr.unregisterListener(this, SENSOR_ACCELEROMETER);
// TODO show an error
}
// NOTE 1: you cannot get system services before onCreate()
// NOTE 2: AndroidManifest.xml must contain this line:
// <uses-permission android:name="android.permission.VIBRATE"/>
Vibrator vibrator = (Vibrator) getSystemService(Activity.VIBRATOR_SERVICE);
for (int i = 0; i < Totalcircles; i++) {
model[i].setVibrator(vibrator);
}
}
public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height) {
for (int i = 0; i < Totalcircles; i++) {
model[i].setSize(width, height);
}
}
public void surfaceCreated(SurfaceHolder holder) {
gameLoop = new GameLoop();
gameLoop.start();
}
private void draw() {
// thread safety - the SurfaceView could go away while we are drawing
Canvas c = null;
try {
// NOTE: in the LunarLander they don't have any synchronization here,
// so I guess this is OK. It will return null if the holder is not ready
c = holder.lockCanvas();
// this needs to synchronize on something
if (c != null) {
doDraw(c);
}
} finally {
if (c != null) {
holder.unlockCanvasAndPost(c);
}
}
}
private void doDraw(Canvas c) {
int width = c.getWidth();
int height = c.getHeight();
c.drawRect(0, 0, width, height, backgroundPaint);
///
float ballX[]=new float[50], ballY[]=new float[50];
for (int i = 0; i < Totalcircles; i++) {
synchronized (model[i].LOCK) {
ballX[i] = model[i].ballPixelX;
ballY[i] = model[i].ballPixelY;
}
}
//
for (int i = 0; i < Totalcircles; i++) {
c.drawCircle(ballX[i], ballY[i], BALL_RADIUS, ballPaint);
}
}
public void surfaceDestroyed(SurfaceHolder holder) {
try {
for (int i = 0; i < Totalcircles; i++) {
model[i].setSize(0,0);
}
gameLoop.safeStop();
} finally {
gameLoop = null;
}
}
private class GameLoop extends Thread {
private volatile boolean running = true;
public void run() {
while (running) {
try {
// don't like this hardcoding
TimeUnit.MILLISECONDS.sleep(5);
draw();
for (int i = 0; i < Totalcircles; i++) {
model[i].updatePhysics();
}
} catch (InterruptedException ie) {
running = false;
}
}
}
public void safeStop() {
running = false;
interrupt();
}
}
public void onAccuracyChanged(int sensor, int accuracy) {
}
public void onSensorChanged(int sensor, float[] values) {
if (sensor == SENSOR_ACCELEROMETER) {
long curTime = System.currentTimeMillis();
// only allow one update every 50ms, otherwise updates
// come way too fast
if (lastSensorUpdate == -1 || (curTime - lastSensorUpdate) > 50) {
lastSensorUpdate = curTime;
for (int i = 0; i < Totalcircles; i++) {
model[i].setAccel(values[DATA_X], values[DATA_Y]);
}
}
}
}
}
model.java
package com.stuffthathappens.games;
import java.util.concurrent.atomic.AtomicReference;
import android.os.Vibrator;
/**
* This data model tracks the width and height of the playing field along
* with the current position of a ball.
*/
public class BouncingBallModel {
// the ball speed is meters / second. When we draw to the screen,
// 1 pixel represents 1 meter. That ends up too slow, so multiply
// by this number. Bigger numbers speeds things up.
private final float pixelsPerMeter = 10;
private final int ballRadius;
// these are public, so make sure you synchronize on LOCK
// when reading these. I made them public since you need to
// get both X and Y in pairs, and this is more efficient than
// getter methods. With two getters, you'd still need to
// synchronize.
public float ballPixelX, ballPixelY;
private int pixelWidth, pixelHeight;
// values are in meters/second
private float velocityX, velocityY;
// typical values range from -10...10, but could be higher or lower if
// the user moves the phone rapidly
private float accelX, accelY;
/**
* When the ball hits an edge, multiply the velocity by the rebound.
* A value of 1.0 means the ball bounces with 100% efficiency. Lower
* numbers simulate balls that don't bounce very much.
*/
private static final float rebound = 0.8f;
// if the ball bounces and the velocity is less than this constant,
// stop bouncing.
private static final float STOP_BOUNCING_VELOCITY = 2f;
private volatile long lastTimeMs = -1;
public final Object LOCK = new Object();
private AtomicReference<Vibrator> vibratorRef =
new AtomicReference<Vibrator>();
public BouncingBallModel(int ballRadius) {
this.ballRadius = ballRadius;
}
public void setAccel(float ax, float ay) {
synchronized (LOCK) {
this.accelX = ax;
this.accelY = ay;
}
}
public void setSize(int width, int height) {
synchronized (LOCK) {
this.pixelWidth = width;
this.pixelHeight = height;
}
}
public int getBallRadius() {
return ballRadius;
}
/**
* Call this to move the ball to a particular location on the screen. This
* resets the velocity to zero, but the acceleration doesn't change so
* the ball should start falling shortly.
*/
public void moveBall(int ballX, int ballY) {
synchronized (LOCK) {
this.ballPixelX = ballX;
this.ballPixelY = ballY;
velocityX = 0;
velocityY = 0;
}
}
public void updatePhysics() {
// copy everything to local vars (hence the 'l' prefix)
float lWidth, lHeight, lBallX, lBallY, lAx, lAy, lVx, lVy;
synchronized (LOCK) {
lWidth = pixelWidth;
lHeight = pixelHeight;
lBallX = ballPixelX;
lBallY = ballPixelY;
lVx = velocityX;
lVy = velocityY;
lAx = accelX;
lAy = -accelY;
}
if (lWidth <= 0 || lHeight <= 0) {
// invalid width and height, nothing to do until the GUI comes up
return;
}
long curTime = System.currentTimeMillis();
if (lastTimeMs < 0) {
lastTimeMs = curTime;
return;
}
long elapsedMs = curTime - lastTimeMs;
lastTimeMs = curTime;
// update the velocity
// (divide by 1000 to convert ms to seconds)
// end result is meters / second
lVx += ((elapsedMs * lAx) / 1000) * pixelsPerMeter;
lVy += ((elapsedMs * lAy) / 1000) * pixelsPerMeter;
// update the position
// (velocity is meters/sec, so divide by 1000 again)
lBallX += ((lVx * elapsedMs) / 1000) * pixelsPerMeter;
lBallY += ((lVy * elapsedMs) / 1000) * pixelsPerMeter;
boolean bouncedX = false;
boolean bouncedY = false;
if (lBallY - ballRadius < 0) {
lBallY = ballRadius;
lVy = -lVy * rebound;
bouncedY = true;
} else if (lBallY + ballRadius > lHeight) {
lBallY = lHeight - ballRadius;
lVy = -lVy * rebound;
bouncedY = true;
}
if (bouncedY && Math.abs(lVy) < STOP_BOUNCING_VELOCITY) {
lVy = 0;
bouncedY = false;
}
if (lBallX - ballRadius < 0) {
lBallX = ballRadius;
lVx = -lVx * rebound;
bouncedX = true;
} else if (lBallX + ballRadius > lWidth) {
lBallX = lWidth - ballRadius;
lVx = -lVx * rebound;
bouncedX = true;
}
if (bouncedX && Math.abs(lVx) < STOP_BOUNCING_VELOCITY) {
lVx = 0;
bouncedX = false;
}
// safely copy local vars back to object fields
synchronized (LOCK) {
ballPixelX = lBallX;
ballPixelY = lBallY;
velocityX = lVx;
velocityY = lVy;
}
if (bouncedX || bouncedY) {
Vibrator v = vibratorRef.get();
if (v != null) {
v.vibrate(20L);
}
}
}
public void setVibrator(Vibrator v) {
vibratorRef.set(v);
}
}
Logcat error:
FATAL EXCEPTION: main
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.stuffthathappens.games/com.stuffthathappens.games.BouncingBallActivity}: java.lang.NullPointerException
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2194)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2229)
at android.app.ActivityThread.access$600(ActivityThread.java:139)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1261)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:154)
at android.app.ActivityThread.main(ActivityThread.java:4945)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:511)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:784)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:551)
at dalvik.system.NativeStart.main(Native Method)
Caused by: java.lang.NullPointerException
at com.stuffthathappens.games.BouncingBallActivity.onCreate(BouncingBallActivity.java:52)
at android.app.Activity.performCreate(Activity.java:4531)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1071)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2150)
You can declare you array like this:
private BouncingBallModel[] model = new BouncingBallModel[50];
like the following
How do I declare and initialize an array in Java?
The wheel View (CircleView) is working fine in major of devices but this error coming from S4 and Note 3 devices.
The touch is getting deducted but the that not fall under the weidgetregion.
false - 1 has to be true - 1
Region Log is:
My Circle View code is
public class CircleView extends View implements OnTouchListener{
boolean firstTime = false;
private List<CircleViewBean> mMenuEntries = new ArrayList<CircleViewBean>();
private OnCellTouchListener mOnCellTouchListener = null;
public interface OnCellTouchListener {
public void onTouch(Wedge cell);
}
private Shader mShader;
private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
private float screen_density = getContext().getResources().getDisplayMetrics().density;
//Radius of inner ring size
private int mMinSize = scalePX(60);
//Radius of outer ring size
private int mMaxSize = scalePX(170);
private int mWedgeQty = 6;
//Center X location of Radial Menu
private int xPosition = scalePX(120);
//Center Y location of Radial Menu
private int yPosition = scalePX(120);
int touchIndex = -1;
private Wedge[] mWedges;
private RectF mViewRect = new RectF();
private int scalePX( int dp_size )
{
return (int) (dp_size * screen_density + 0.5f);
}
public CircleView(Context context) {
this(context, null);
}
public CircleView(Context context, AttributeSet attrs) {
super(context, attrs);
HashMap<String, String> device = Constants.getDeviceDetails(getResources().getDisplayMetrics().heightPixels, getResources().getDisplayMetrics().widthPixels);
mMinSize = Integer.parseInt(device.get("in_arc"));
mMaxSize = Integer.parseInt(device.get("out_arc"));
setBackgroundResource(R.drawable.centre_wheel);
}
private void determineWedges() {
int entriesQty = mMenuEntries.size();
if ( entriesQty > 0) {
mWedgeQty = entriesQty;
float degSlice = 360 / mWedgeQty;
float start_degSlice = 270 - (degSlice/2);
//calculates where to put the images
this.mWedges = new Wedge[mWedgeQty];
double mid = 0, min = 0, max = 0;
for(int i = 0; i < this.mWedges.length; i++) {
this.mWedges[i] = new Wedge(xPosition, yPosition, mMinSize, mMaxSize, (i
* degSlice)+start_degSlice, degSlice, mMenuEntries.get(i).getIndex());
mid = this.mWedges[i].midValue = normalizeAngle( ((i * degSlice) + start_degSlice + degSlice) / 2 );
min = normalizeAngle( (i * degSlice) + start_degSlice );
max = normalizeAngle( (i * degSlice) + start_degSlice + degSlice);
this.mWedges[i].minValue = min;
this.mWedges[i].midValue = mid;
this.mWedges[i].maxValue = max;
mViewRect.union( new RectF( mWedges[i].getWedgeRegion().getBounds() ) );
}
mShader = new RadialGradient(xPosition, yPosition, mMaxSize, new int[] { 0xff595756, 0xffCCC5C3, 0xf878280}, null, Shader.TileMode.MIRROR);
invalidate();
}
}
#Override
public void onDraw(Canvas canvas) {
if(!firstTime){
firstTime = true;
this.xPosition = (int) (getWidth()/2f);
this.yPosition = (int) (getHeight()/2f);
determineWedges();
}
canvas.scale(getWidth() / mViewRect.width(), getHeight() / mViewRect.width(), xPosition, yPosition);
//Saving the canvas and later restoring it so only this image will be rotated.
canvas.save(Canvas.MATRIX_SAVE_FLAG);
canvas.restore();
canvas.save();
canvas.restore();
mPaint.setShader( mShader );
}
private double normalizeAngle(double angle) {
if(angle >= 0) {
while( angle > 360 ) {
angle -= 360;
}
}
else {
while( angle < -360) {
angle += 360;
}
}
return angle;
}
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// TODO Auto-generated method stub
int wmode = MeasureSpec.getMode(widthMeasureSpec);
int hmode = MeasureSpec.getMode(heightMeasureSpec);
int wsize = MeasureSpec.getSize(widthMeasureSpec);
int hsize = MeasureSpec.getSize(heightMeasureSpec);
int width = (int)mViewRect.width();
int height = (int) mViewRect.height();
if (wmode == MeasureSpec.EXACTLY) {
width = wsize;
}
if (hmode == MeasureSpec.EXACTLY) {
height = hsize;
}
this.setMeasuredDimension(width, height);
invalidate();
}
public class Wedge extends Path {
private int x, y;
private int InnerSize, OuterSize;
private float StartArc;
private float ArcWidth;
private Region mWedgeRegion;
private int index=0;
public double minValue;
public double midValue;
public double maxValue;
private Wedge(int x, int y, int InnerSize, int OuterSize, float StartArc, float ArcWidth, int category) {
super();
this.index = category;
if (StartArc >= 360) {
StartArc = StartArc-360;
}
minValue = midValue = maxValue = 0;
mWedgeRegion = new Region();
this.x = x; this.y = y;
this.InnerSize = InnerSize;
this.OuterSize = OuterSize;
this.StartArc = StartArc;
this.ArcWidth = ArcWidth;
this.buildPath();
}
public int getCategoryIndex(){
return this.index;
}
public String toString() {
return minValue + " " + midValue + " " + maxValue;
}
/**
*
* #return the bottom rect that will be used for intersection
*/
public Region getWedgeRegion() {
return mWedgeRegion;
}
private void buildPath() {
final RectF rect = new RectF();
final RectF rect2 = new RectF();
//Rectangles values
rect.set(this.x-this.InnerSize, this.y-this.InnerSize, this.x+this.InnerSize, this.y+this.InnerSize);
rect2.set(this.x-this.OuterSize, this.y-this.OuterSize, this.x+this.OuterSize, this.y+this.OuterSize);
this.reset();
//this.moveTo(100, 100);
this.arcTo(rect2, StartArc, ArcWidth);
this.arcTo(rect, StartArc+ArcWidth, -ArcWidth);
this.close();
mWedgeRegion.setPath( this, new Region(0,0,480,800) );
}
}
public boolean addMenuEntry(CircleViewBean menuEntry) {
mMenuEntries.add(menuEntry);
return true;
}
#Override
public boolean onTouchEvent(MotionEvent event) {
if(event.getAction() == MotionEvent.ACTION_UP){
if(mOnCellTouchListener!=null && touchIndex >-1){
int i=0;
for(Wedge day : mWedges) {
if(day.getWedgeRegion().getBounds().contains((int)event.getX(), (int)event.getY()) && touchIndex==i) {
mOnCellTouchListener.onTouch(mWedges[touchIndex]);
break;
}
i++;
}
}
}else if(event.getAction() == MotionEvent.ACTION_DOWN || event.getAction() == MotionEvent.ACTION_MOVE){
int i=0;
for(Wedge day : mWedges) {
if(day.getWedgeRegion().getBounds().contains((int)event.getX(), (int)event.getY())) {
touchIndex = i;
setBackgroundResource(mMenuEntries.get(touchIndex).getIcon());
}
i++;
}
}
return true;
}
public void setOnCellTouchListener(OnCellTouchListener p) {
mOnCellTouchListener = p;
}
public boolean onTouch(View v, MotionEvent event) {
return false;
}
}
First of all, look at line 307. Learn how to read crash logs because it says exactly on what line the crash is, and then it shouldn't be too hard too determine what is wrong.
Not knowing what line it is I guess that mWedges might be null. in the onTouch you do for(Wedge day : mWedges) but it is not guaranteed that is isn't null there. You should check before you do that if it is null.
You put it to a non null value in determineWedges but only when there is at least 1 mMenuEntries. So when there are no entries when you do an onTouch it will crash.
At last i found my mistake in this code that it is working in mdpi and htpi and not in xxhdpi the reason is
mWedgeRegion.setPath( this, new Region(0,0,480,800) );
The bound exceeds the circle size, i mean the xxhdpi draws an circle with 1000x1000 values, so i made this too like this (max values)
mWedgeRegion.setPath( this, new Region(0,0,720,1200) )
I had tried alot & even search alot but i didn't found
the solution about the black screen
which i get by fetching the cache view on the surface view..
if is there any other way to capture the screen then let me know about this..
if i use another control & fetching the drawable cache of that control it also return null..
this is the code which i used to fetch the screen Image....
try {
// Button btn = new Button(mActivity.getApplicationContext());
view.buildDrawingCache();
view.setDrawingCacheEnabled(true);
Bitmap b = view.getDrawingCache();
b.compress(CompressFormat.JPEG, 100, new FileOutputStream(
"/mnt/sdcard/documents/" + new Date().getTime() + ".JPEG"));
} catch (Exception e) {
e.printStackTrace();
}
i had used this on the touch_up action of the surface view.....
EDIT:
public class DroidReaderActivity extends Activity {
private static final boolean LOG = false;
private static final int REQUEST_CODE_PICK_FILE = 1;
private static final int REQUEST_CODE_OPTION_DIALOG = 2;
private static final int DIALOG_GET_PASSWORD = 1;
private static final int DIALOG_ABOUT = 2;
private static final int DIALOG_GOTO_PAGE = 3;
private static final int DIALOG_WELCOME = 4;
private static final int DIALOG_ENTER_ZOOM = 5;
private static final String PREFERENCE_EULA_ACCEPTED = "eula.accepted";
private static final String PREFERENCES_EULA = "eula";
protected DroidReaderView mReaderView = null;
protected DroidReaderDocument mDocument = null;
protected Menu m_ZoomMenu;
FrameLayout fl;
private String mFilename;
private String mTemporaryFilename;
private String mPassword;
private int mPageNo;
private SQLiteDatabase db;
static DatabaseConnectionAPI db_api;
private boolean mDocumentIsOpen = false;
private boolean mLoadedDocument = false;
private boolean mWelcomeShown = false;
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
switch (requestCode) {
case REQUEST_CODE_PICK_FILE:
if (resultCode == RESULT_OK && data != null) {
// Theoretically there could be a case where OnCreate() is called
// again with the intent that was originally used to open the app,
// which would revert to a previous document. Use setIntent
// to update the intent that will be supplied back to OnCreate().
setIntent(data);
mTemporaryFilename = data.getDataString();
if (mTemporaryFilename != null) {
if (mTemporaryFilename.startsWith("file://")) {
mTemporaryFilename = mTemporaryFilename.substring(7);
}
mPassword = "";
openDocumentWithDecodeAndLookup();
}
}
break;
case REQUEST_CODE_OPTION_DIALOG:
readPreferences();
tryLoadLastFile();
break;
}
}
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
System.out.println("ONCREATE");
db_api = new DatabaseConnectionAPI(this);
try {
db_api.createDataBase();
db_api.openDataBase();
} catch (IOException e) {
e.printStackTrace();
}
// first, show the welcome if it hasn't been shown already:
final SharedPreferences preferences = getSharedPreferences(PREFERENCES_EULA, Context.MODE_PRIVATE);
if (!preferences.getBoolean(PREFERENCE_EULA_ACCEPTED, false)) {
mWelcomeShown = true;
preferences.edit().putBoolean(PREFERENCE_EULA_ACCEPTED, true).commit();
showDialog(DIALOG_WELCOME);
}
if (mDocument == null)
mDocument = new DroidReaderDocument();
// Initialize the PdfRender engine
PdfRender.setFontProvider(new DroidReaderFontProvider(this));
// then build our layout. it's so simple that we don't use
// XML for now.
fl = new FrameLayout(this);
mReaderView = new DroidReaderView(this, null, mDocument);
// add the viewing area and the navigation
fl.addView(mReaderView);
setContentView(fl);
readPreferences();
if (savedInstanceState != null) {
mFilename = savedInstanceState.getString("filename");
if ((new File(mFilename)).exists()) {
mPassword = savedInstanceState.getString("password");
mDocument.mZoom = savedInstanceState.getFloat("zoom");
mDocument.mRotation = savedInstanceState.getInt("rotation");
mPageNo = savedInstanceState.getInt("page");
mDocument.mMarginOffsetX = savedInstanceState.getInt("marginOffsetX");
mDocument.mMarginOffsetY = savedInstanceState.getInt("marginOffsetY");
mDocument.mContentFitMode = savedInstanceState.getInt("contentFitMode");
openDocument();
mLoadedDocument = true;
}
savedInstanceState.clear();
}
Timer mTimer = new Timer();
mTimer.schedule(new TimerTask() {
#Override
public void run() {
try {
Bitmap saveBitmap = Bitmap.createBitmap(fl.getWidth(), fl.getHeight(), Bitmap.Config.ARGB_8888);
saveBitmap.compress(CompressFormat.JPEG, 100,
new FileOutputStream("/mnt/sdcard/documents/" + new Date().getTime() + ".JPEG"));
} catch (Exception e) {
e.printStackTrace();
}
}
}, 4000);
}
}
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
if ((mDocument != null) && mDocument.isPageLoaded()) {
outState.putFloat("zoom", mDocument.mZoom);
outState.putInt("rotation", mDocument.mRotation);
outState.putInt("page", mDocument.mPage.no);
outState.putInt("offsetX", mDocument.mOffsetX);
outState.putInt("offsetY", mDocument.mOffsetY);
outState.putInt("marginOffsetX", mDocument.mMarginOffsetX);
outState.putInt("marginOffsetY", mDocument.mMarginOffsetY);
outState.putInt("contentFitMode", mDocument.mContentFitMode);
outState.putString("password", mPassword);
outState.putString("filename", mFilename);
mDocument.closeDocument();
}
}
public void onTap(float X, float Y) {
float left, right, top, bottom;
float width = mDocument.mDisplaySizeX;
float height = mDocument.mDisplaySizeY;
boolean prev = false;
boolean next = false;
if (mDocumentIsOpen) {
left = width * (float) 0.25;
right = width * (float) 0.75;
top = height * (float) 0.25;
bottom = height * (float) 0.75;
if ((X < left) && (Y < top))
prev = true;
if ((X < left) && (Y > bottom))
next = true;
if ((X > right) && (Y < top))
prev = true;
if ((X > right) && (Y > bottom))
next = true;
if ((X > left) && (X < right) && (Y > bottom)) {
Log.d("DroidReaderMetrics", String.format("Zoom = %5.2f%%", mDocument.mZoom * 100.0));
Log.d("DroidReaderMetrics", String.format("Page size = (%2.0f,%2.0f)", mDocument.mPage.mMediabox[2]
- mDocument.mPage.mMediabox[0], mDocument.mPage.mMediabox[3] - mDocument.mPage.mMediabox[1]));
Log.d("DroidReaderMetrics", String.format(
"Display size = (%d,%d)", mDocument.mDisplaySizeX, mDocument.mDisplaySizeY));
Log.d("DroidReaderMetrics", String.format("DPI = (%d, %d)", mDocument.mDpiX, mDocument.mDpiY));
Log.d("DroidReaderMetrics", String.format("Content size = (%2.0f,%2.0f)",
mDocument.mPage.mContentbox[2] - mDocument.mPage.mContentbox[0],
mDocument.mPage.mContentbox[3] - mDocument.mPage.mContentbox[1]));
Log.d("DroidReaderMetrics", String.format("Content offset = (%2.0f,%2.0f)",
mDocument.mPage.mContentbox[0], mDocument.mPage.mContentbox[1]));
Log.d("DroidReaderMetrics", String.format(
"Document offset = (%d,%d)", mDocument.mOffsetX, mDocument.mOffsetY));
}
if (next) {
if (mDocument.havePage(1, true))
openPage(1, true);
} else if (prev) {
if (mDocument.havePage(-1, true))
openPage(-1, true);
}
}
}
protected void openDocument() {
// Store the view details for the previous document and close it.
if (mDocumentIsOpen) {
mDocument.closeDocument();
mDocumentIsOpen = false;
}
try {
this.setTitle(mFilename);
mDocument.open(mFilename, mPassword, mPageNo);
openPage(0, true);
mDocumentIsOpen = true;
} catch (PasswordNeededException e) {
showDialog(DIALOG_GET_PASSWORD);
} catch (WrongPasswordException e) {
Toast.makeText(this, R.string.error_wrong_password, Toast.LENGTH_LONG).show();
} catch (Exception e) {
Toast.makeText(this, R.string.error_opening_document, Toast.LENGTH_LONG).show();
}
}
protected void openDocumentWithDecodeAndLookup() {
try {
mTemporaryFilename = URLDecoder.decode(mTemporaryFilename, "utf-8");
// Do some sanity checks on the supplied filename.
File f = new File(mTemporaryFilename);
if ((f.exists()) && (f.isFile()) && (f.canRead())) {
mFilename = mTemporaryFilename;
openDocumentWithLookup();
} else {
Toast.makeText(this, R.string.error_file_open_failed, Toast.LENGTH_LONG).show();
}
} catch (Exception e) {
Toast.makeText(this, R.string.error_opening_document, Toast.LENGTH_LONG).show();
}
}
protected void openDocumentWithLookup() {
readOrWriteDB(false);
openDocument();
}
protected void openPage(int no, boolean isRelative) {
try {
if (!(no == 0 && isRelative))
mDocument.openPage(no, isRelative);
this.setTitle(new File(mFilename).getName()
+ String.format(" (%d/%d)", mDocument.mPage.no, mDocument.mDocument.pagecount));
mPageNo = mDocument.mPage.no;
} catch (PageLoadException e) {
}
}
private void readPreferences() {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
DisplayMetrics metrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(metrics);
if (prefs.getString("zoom_type", "0").equals("0")) {
float zoom = Float.parseFloat(prefs.getString("zoom_percent", "50"));
if ((1 <= zoom) && (1000 >= zoom)) {
mDocument.setZoom(zoom / 100, false);
}
} else {
mDocument.setZoom(Float.parseFloat(prefs.getString("zoom_type", "0")), false);
}
if (prefs.getBoolean("dpi_auto", true)) {
// read the display's DPI
mDocument.setDpi((int) metrics.xdpi, (int) metrics.ydpi);
} else {
int dpi = Integer.parseInt(prefs.getString("dpi_manual", "160"));
if ((dpi < 1) || (dpi > 4096))
dpi = 160; // sanity check fallback
mDocument.setDpi(dpi, dpi);
}
if (prefs.getBoolean("tilesize_by_factor", true)) {
// set the tile size for rendering by factor
Float factor = Float.parseFloat(prefs.getString("tilesize_factor", "1.5"));
mDocument.setTileMax((int) (metrics.widthPixels * factor), (int) (metrics.heightPixels * factor));
} else {
int tilesize_x = Integer.parseInt(prefs.getString("tilesize_x", "640"));
int tilesize_y = Integer.parseInt(prefs.getString("tilesize_x", "480"));
if (metrics.widthPixels < metrics.heightPixels) {
mDocument.setTileMax(tilesize_x, tilesize_y);
} else {
mDocument.setTileMax(tilesize_y, tilesize_x);
}
}
boolean invert = prefs.getBoolean("invert_display", false);
mDocument.setDisplayInvert(invert);
mReaderView.setDisplayInvert(invert);
if (prefs.getBoolean("full_screen", false)) {
this.getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
} else {
this.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
}
mDocument.mHorizontalScrollLock = prefs.getBoolean("horizontal_scroll_lock", false);
}
protected void setZoom(float newZoom) {
newZoom = newZoom / (float) 100.0;
if (newZoom > 16.0)
newZoom = (float) 16.0;
if (newZoom < 0.0625)
newZoom = (float) 0.0625;
mDocument.setZoom(newZoom, false);
}
protected void tryLoadLastFile() {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
mFilename = prefs.getString("last_open_file", "");
if (mFilename != null) {
if ((mFilename.length() > 0) && ((new File(mFilename)).exists())) {
// Don't URL-decode the filename, as that's presumably already been done.
mPassword = "";
openDocumentWithLookup();
mLoadedDocument = true;
}
}
}
}
DroidReaderView:
public class DroidReaderView extends SurfaceView implements OnGestureListener,
SurfaceHolder.Callback, DroidReaderDocument.RenderListener {
public static Path mPath = new Path();
private static StringBuffer sbx = new StringBuffer();
private static StringBuffer sby = new StringBuffer();
public static Paint mPaint = new Paint();
public static Paint nullpaint = new Paint();
private String sx, sy, sbx_str, sby_str;
public static Canvas mCanvas = new Canvas();
private int pid = 1;
/**
* Debug helper
*/
protected final static String TAG = "DroidReaderView";
protected final static boolean LOG = false;
/**
* our view thread which does the drawing
*/
public DroidReaderViewThread mThread;
/**
* our gesture detector
*/
protected final GestureDetector mGestureDetector;
/**
* our context
*/
protected final DroidReaderActivity mActivity;
/**
* our SurfaceHolder
*/
protected final SurfaceHolder mSurfaceHolder;
public static DroidReaderDocument mDocument;
protected boolean mDisplayInvert;
/**
* constructs a new View
*
* #param context
* Context for the View
* #param attrs
* attributes (may be null)
*/
public DroidReaderView(final DroidReaderActivity activity, AttributeSet attrs, DroidReaderDocument document) {
super(activity, attrs);
mActivity = activity;
mSurfaceHolder = getHolder();
mDocument = document;
mDocument.mRenderListener = this;
mPaint.setAntiAlias(true);
mPaint.setDither(true);
mPaint.setColor(Color.BLACK);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeJoin(Paint.Join.ROUND);
mPaint.setStrokeCap(Paint.Cap.ROUND);
mPaint.setStrokeWidth(12);
// tell the SurfaceHolder to inform this thread on
// changes to the surface
mSurfaceHolder.addCallback(this);
mGestureDetector = new GestureDetector(this);
}
/* event listeners: */
#Override
protected void onDraw(Canvas canvas) {
mThread.c = canvas;
mThread.c = mSurfaceHolder.lockCanvas();
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setDither(true);
mPaint.setColor(Color.RED);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeJoin(Paint.Join.ROUND);
mPaint.setStrokeCap(Paint.Cap.ROUND);
mPaint.setStrokeWidth(12);
mThread.c.drawPath(mPath, mPaint);
mSurfaceHolder.unlockCanvasAndPost(mThread.c);
}
#Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
if (LOG)
Log.d(TAG, "onFling(): notifying ViewThread");
mThread.mScroller.fling(0, 0, -(int) velocityX, -(int) velocityY, -4096, 4096, -4096, 4096);
mThread.triggerRepaint();
return true;
}
/* keyboard events: */
#Override
public boolean onKeyDown(int keyCode, KeyEvent msg) {
if (LOG)
Log.d(TAG, "onKeyDown(), keycode " + keyCode);
return false;
}
#Override
public boolean onKeyUp(int keyCode, KeyEvent msg) {
if (LOG)
Log.d(TAG, "onKeyUp(), keycode " + keyCode);
return false;
}
/* interface for the GestureListener: */
#Override
public void onLongPress(MotionEvent e) {
if (LOG)
Log.d(TAG, "onLongPress(): ignoring!");
}
#Override
public void onNewRenderedPixmap() {
if (LOG)
Log.d(TAG, "new rendered pixmap was signalled");
mThread.triggerRepaint();
}
#Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
if (LOG)
Log.d(TAG, "onScroll(), distance vector: " + distanceX + "," + distanceY);
mDocument.offset((int) distanceX, (int) distanceY, true);
mThread.triggerRepaint();
return true;
}
#Override
public void onShowPress(MotionEvent e) {
if (LOG)
Log.d(TAG, "onShowPress(): ignoring!");
}
#Override
public boolean onSingleTapUp(MotionEvent e) {
// Pass the tap, and the window dimensions, to the activity to process.
mActivity.onTap(e.getX(), e.getY());
return true;
}
private float mX, mY;
private static final float TOUCH_TOLERANCE = 4;
private void touch_start(float x, float y) {
mPath.moveTo(x, y);
mX = x;
sx = Float.toString(mX);
sbx.append(sx);
sbx.append(",");
mY = y;
sy = Float.toString(mY);
sby.append(sy);
sby.append(",");
sbx_str = sbx.toString();
sby_str = sby.toString();
}
private void draw_move(float x, float y) {
float dx = Math.abs(x - mX);
float dy = Math.abs(y - mY);
if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE) {
mPath.quadTo(mX, mY, (x + mX) / 2, (y + mY) / 2);
mX = x;
sx = Float.toString(mX);
sbx.append(sx);
sbx.append(",");
mY = y;
sy = Float.toString(mY);
sby.append(sy);
sby.append(",");
}
}
private void touch_up() {
mPath.lineTo(mX, mY);
sx = Float.toString(mX);
sbx.append(sx);
sbx.append(",");
sy = Float.toString(mY);
sby.append(sy);
sby.append(",");
mPath.reset();
sbx_str = sbx.toString().trim();
sby_str = sby.toString().trim();
insert(TAGS.presentation_id, mDocument.mPage.no, sbx_str, sby_str);
sbx = new StringBuffer();
sby = new StringBuffer();
System.out.println(sbx_str.trim());
System.out.println(sby_str.trim());
}
#Override
public boolean onTouchEvent(final MotionEvent event) {
float x = event.getX();
float y = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
touch_start(x, y);
invalidate();
break;
case MotionEvent.ACTION_MOVE:
draw_move(x, y);
invalidate();
break;
case MotionEvent.ACTION_UP:
touch_up();
invalidate();
try {
// Button btn = new Button(mActivity.getApplicationContext());
// buildDrawingCache();
// setDrawingCacheEnabled(true);
// Bitmap b = getDrawingCache();
// b.compress(CompressFormat.JPEG, 100, new FileOutputStream(
// "/mnt/sdcard/documents/" + new Date().getTime() + ".JPEG"));
} catch (Exception e) {
e.printStackTrace();
}
break;
}
invalidate();
if (LOG) {
Log.d(TAG, "onTouchEvent(): notifying mGestureDetector");
invalidate();
}
if (mGestureDetector.onTouchEvent(event)) {
invalidate();
return true;
}
return true;
}
#Override
public boolean onTrackballEvent(MotionEvent event) {
if (LOG)
Log.d(TAG, "onTouchEvent(): notifying ViewThread");
mDocument
.offset((int) event.getX() * 20, (int) event.getY() * 20, true);
mThread.triggerRepaint();
return true;
}
/* surface events: */
public void setDisplayInvert(boolean invert) {
if (mThread != null)
mThread.setPainters(invert);
mDisplayInvert = invert;
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height) {
if (LOG)
Log.d(TAG, "surfaceChanged(): size " + width + "x" + height);
mDocument.startRendering(width, height);
}
#Override
public void surfaceCreated(SurfaceHolder holder) {
if (LOG)
Log.d(TAG, "surfaceCreated(): starting ViewThread");
mThread = new DroidReaderViewThread(holder, mActivity, mDocument);
mThread.setPainters(mDisplayInvert);
mThread.start();
}
/* render events */
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
if (LOG)
Log.d(TAG, "surfaceDestroyed(): dying");
mDocument.stopRendering();
boolean retry = true;
mThread.mRun = false;
mThread.interrupt();
while (retry) {
try {
mThread.join();
retry = false;
} catch (InterruptedException e) {
}
}
}
#Override
public boolean onDown(MotionEvent e) {
return false;
}
DroidReaderViewThread :
Thread that cares for blitting Pixmaps onto the Canvas and handles scrolling
class DroidReaderViewThread extends Thread {
public Canvas c = new Canvas();
private Cursor mCursor;
private Path mPath1 = new Path();
private StringBuffer sbx_read, sby_read;
public static Paint mPaint2 = new Paint();
public static Paint mPaint3 = new Paint();
Path old_path = new Path();
/**
* Debug helper
*/
protected final static String TAG = "DroidReaderViewThread";
protected final static boolean LOG = false;
/**
* the SurfaceHolder for our Surface
*/
protected final SurfaceHolder mSurfaceHolder;
/**
* Paint for not (yet) rendered parts of the page
*/
protected final Paint mEmptyPaint;
/**
* Paint for filling the display when there is no PdfPage (yet)
*/
protected final Paint mNoPagePaint;
/**
* Paint for the status text
*/
protected final Paint mStatusPaint;
/**
* Flag that our thread should be running
*/
protected boolean mRun = true;
/**
* our scroller
*/
protected final Scroller mScroller;
protected final DroidReaderDocument mDocument;
/**
* Background render thread, using the SurfaceView programming scheme
*
* #param holder
* our SurfaceHolder
* #param context
* the Context for our drawing
*/
public DroidReaderViewThread(SurfaceHolder holder, Context context, DroidReaderDocument document) {
// store a reference to our SurfaceHolder
mSurfaceHolder = holder;
mDocument = document;
// initialize Paints for non-Pixmap areas
mEmptyPaint = new Paint();
mNoPagePaint = new Paint();
mStatusPaint = new Paint();
setPainters(false);
// the scroller, i.e. the object that calculates/interpolates
// positions for scrolling/jumping/flinging
mScroller = new Scroller(context);
}
/**
* ll this does the actual drawing to the Canvas for our surface
*/
private void doDraw() {
if (LOG)
Log.d(TAG, "drawing...");
c = null;
try {
c = mSurfaceHolder.lockCanvas(null);
if (!mDocument.isPageLoaded()) {
// no page/document loaded
if (LOG)
Log.d(TAG, "no page loaded.");
c.drawRect(0, 0, c.getWidth(), c.getHeight(), mNoPagePaint);
} else if (mDocument.havePixmap()) {
// we have both page and Pixmap, so draw:
// background:
if (LOG)
Log.d(TAG, "page loaded, rendering pixmap");
c.drawRect(0, 0, c.getWidth(), c.getHeight(), mEmptyPaint);
Log.d("CALL", "CALL");
c.drawBitmap(mDocument.mView.mBuf, 0,
mDocument.mView.mViewBox.width(), -mDocument.mOffsetX + mDocument.mView.mViewBox.left,
-mDocument.mOffsetY + mDocument.mView.mViewBox.top,
mDocument.mView.mViewBox.width(), mDocument.mView.mViewBox.height(), false, null);
try {
Log.d("Reading", "Reading");
mCursor = DroidReaderActivity.db_api
.ExecuteQueryGetCursor("SELECT * FROM path WHERE page_no=" + mDocument.mPage.no
+ " AND presentation_id=" + TAGS.presentation_id + ";");
if (!mCursor.equals(null)) {
mCursor.moveToFirst();
float x1 = 0, y1 = 0;
int pid = 0;
do {
sbx_read = new StringBuffer();
sbx_read.append(mCursor.getString(mCursor.getColumnIndex("x_path")));
sby_read = new StringBuffer();
sby_read.append(mCursor.getString(mCursor.getColumnIndex("y_path")));
String[] sbx_read_array = sbx_read.toString().trim().split(",");
String[] sby_read_array = sby_read.toString().trim().split(",");
for (int i = 0; i < sbx_read_array.length; i++) {
x1 = Float.parseFloat(sbx_read_array[i].toString());
y1 = Float.parseFloat(sby_read_array[i].toString());
if (pid != mCursor.getInt(mCursor.getColumnIndex("path_id"))) {
pid = mCursor.getInt(mCursor.getColumnIndex("path_id"));
Log.d("New Path Id.",
String.valueOf(mCursor.getInt(mCursor.getColumnIndex("path_id"))));
mPath1.reset();
mPath1.moveTo(x1, y1);
} else {
Log.d("Path id repeating.",
String.valueOf(mCursor.getInt(mCursor.getColumnIndex("path_id"))));
}
mPath1.lineTo(x1, y1);
c.drawPath(mPath1, DroidReaderView.mPaint);
}
} while (mCursor.moveToNext());
mCursor.close();
Log.d("Read mode Complete", "Read mode Complete");
}
} catch (Exception e) {
// Log.d("read Cursor", e.getMessage().toString());
}
} else {
// page loaded, but no Pixmap yet
if (LOG)
Log.d(TAG, "page loaded, but no active Pixmap.");
c.drawRect(0, 0, c.getWidth(), c.getHeight(), mEmptyPaint);
mPaint3.setAntiAlias(true);
mPaint3.setDither(true);
mPaint3.setColor(Color.TRANSPARENT);
mPaint3.setStyle(Paint.Style.STROKE);
mPaint3.setStrokeJoin(Paint.Join.ROUND);
mPaint3.setStrokeCap(Paint.Cap.ROUND);
mPaint3.setStrokeWidth(12);
mPaint3.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
c.drawPath(old_path, mPaint3);
}
} finally {
if (c != null) {
mPaint2.setAntiAlias(true);
mPaint2.setDither(true);
mPaint2.setColor(Color.GREEN);
mPaint2.setStyle(Paint.Style.STROKE);
mPaint2.setStrokeJoin(Paint.Join.ROUND);
mPaint2.setStrokeCap(Paint.Cap.ROUND);
mPaint2.setStrokeWidth(12);
c.drawPath(DroidReaderView.mPath, mPaint2);
// DroidReaderView.mPath.reset();
// old_path = DroidReaderView.mPath;
}
mSurfaceHolder.unlockCanvasAndPost(c);
}
}
/**
* Main Thread loop
*/
#SuppressWarnings("static-access")
#Override
public void run() {
while (mRun) {
boolean doSleep = true;
if (!mScroller.isFinished()) {
if (mScroller.computeScrollOffset()) {
if (LOG)
Log.d(TAG, "new scroll offset");
doSleep = false;
int oldX = mDocument.mOffsetX;
int oldY = mDocument.mOffsetY;
mDocument.offset(mScroller.getCurrX(), mScroller.getCurrY(), true);
if ((oldX == mDocument.mOffsetX) && (oldY == mDocument.mOffsetY))
mScroller.abortAnimation();
} else {
mScroller.abortAnimation();
}
}
doDraw();
// if we're allowed, we will go to sleep now
if (doSleep) {
try {
// nothing to do, wait for someone waking us up:
if (LOG)
Log.d(TAG, "ViewThread going to sleep");
// between
// the check for pending interrupts and the sleep() which
// could lead to a not-handled repaint request:
if (!this.interrupted())
Thread.sleep(3600000);
} catch (InterruptedException e) {
if (LOG)
Log.d(TAG, "ViewThread woken up");
}
}
}
// mRun is now false, so we shut down.
if (LOG)
Log.d(TAG, "shutting down");
}
public void setPainters(boolean invert) {
// initialize Paints for non-Pixmap areas
mEmptyPaint.setStyle(Paint.Style.FILL);
mNoPagePaint.setStyle(Paint.Style.FILL);
mStatusPaint.setStyle(Paint.Style.FILL);
if (invert)
mEmptyPaint.setColor(0xff000000); // black
else
mEmptyPaint.setColor(0xffc0c0c0); // light gray
if (invert)
mNoPagePaint.setColor(0xff000000); // black
else
mNoPagePaint.setColor(0xff303030); // dark gray
if (invert)
mStatusPaint.setColor(0xff000000); // black
else
mStatusPaint.setColor(0xff808080); // medium gray
}
public void triggerRepaint() {
if (LOG)
Log.d(TAG, "repaint triggered");
interrupt();
}
}
I am using this, and its works fine in my case,
Here imageFrame is FrameLayout, root view of my View which I want to save as bitmap..
Bitmap saveBitmap = Bitmap.createBitmap(imageFrame.getWidth(), imageFrame.getHeight(), Bitmap.Config.ARGB_8888);
Canvas c = new Canvas(saveBitmap);
imageFrame.draw(c);
Try replacing imageFrame with your view.
EDIT:
Date date = new Date();
File filename = new File(file.getAbsoluteFile(), "" + date.getTime() + ".jpg");
try
{
fOut = new FileOutputStream(filename);
saveBitmap.compress(Bitmap.CompressFormat.JPEG, 50, fOut);
try
{
fOut.flush();
fOut.close();
}
catch (IOException e)
{
e.printStackTrace();
}
}
catch (FileNotFoundException e)
{
e.printStackTrace();
}
EDIT 2:
private void doDraw() {
int w = WIDTH_PX, h = HEIGHT_PX;
BitmapConfig conf = Bitmap.Config.ARGB_8888; // see other conf types
Bitmap bmp = Bitmap.createBitmap(w, h, conf); // this creates a MUTABLE bitmap
c = new Canvas(bmp);
This is a code to implement layer concept. i.e adding layers dynamically.I am getting error in the mcallback in //overides from Drawable.callback .
Can someone help me to resolve it. It says mcallback cannot be resolved into a variable.
Thanks in advance
package com.cts.drawable;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.*;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Drawable.Callback;
import android.util.AttributeSet;
import android.view.View;
import java.io.IOException;
/**
* A Drawable that manages an array of other Drawables. These are drawn in array
* order, so the element with the largest index will be drawn on top.
* <p>
* It can be defined in an XML file with the <code><layer-list> element.
* Each Drawable in the layer is defined in a nested <code><item>.
* </p>
*
* #attr ref android.R.styleable#LayerDrawableItem_left
* #attr ref android.R.styleable#LayerDrawableItem_top
* #attr ref android.R.styleable#LayerDrawableItem_right
* #attr ref android.R.styleable#LayerDrawableItem_bottom
* #attr ref android.R.styleable#LayerDrawableItem_drawable
* #attr ref android.R.styleable#LayerDrawableItem_id
*/
public class LayerDrawable extends Drawable implements Drawable.Callback {
LayerState mLayerState;
private int[] mPaddingL;
private int[] mPaddingT;
private int[] mPaddingR;
private int[] mPaddingB;
private final Rect mTmpRect = new Rect();
private boolean mMutated;
/**
* Create a new layer drawable with the list of specified layers.
*
* #param layers A list of drawables to use as layers in this new drawable.
*/
public LayerDrawable(Drawable[] layers) {
this(layers, null);
}
/**
* Create a new layer drawable with the specified list of layers and the specified
* constant state.
*
* #param layers The list of layers to add to this drawable.
* #param state The constant drawable state.
*/
LayerDrawable(Drawable[] layers, LayerState state) {
this(state, null);
int length = layers.length;
ChildDrawable[] r = new ChildDrawable[length];
for (int i = 0; i < length; i++) {
r[i] = new ChildDrawable();
r[i].mDrawable = layers[i];
layers[i].setCallback(this);
mLayerState.mChildrenChangingConfigurations |= layers [i].getChangingConfigurations();
}
mLayerState.mNum = length;
mLayerState.mChildren = r;
ensurePadding();
}
LayerDrawable() {
this((LayerState) null, null);
}
LayerDrawable(LayerState state, Resources res) {
LayerState as = createConstantState(state, res);
mLayerState = as;
if (as.mNum > 0) {
ensurePadding();
}
}
LayerState createConstantState(LayerState state, Resources res) {
return new LayerState(state, this, res);
}
#Override
public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs)
throws XmlPullParserException, IOException {
super.inflate(r, parser, attrs);
int type;
final int innerDepth = parser.getDepth() + 1;
int depth;
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
&& ((depth = parser.getDepth()) >= innerDepth || type != XmlPullParser.END_TAG)) {
if (type != XmlPullParser.START_TAG) {
continue;
}
if (depth > innerDepth || !parser.getName().equals("item")) {
continue;
}
TypedArray a = r.obtainAttributes(attrs,
R.styleable.LayerDrawableItem);
int left = a.getDimensionPixelOffset(
R.styleable.LayerDrawableItem_LayerDrawableItem_left, 0);
int top = a.getDimensionPixelOffset(
R.styleable.LayerDrawableItem_LayerDrawableItem_top, 0);
int right = a.getDimensionPixelOffset(
R.styleable.LayerDrawableItem_LayerDrawableItem_right, 0);
int bottom = a.getDimensionPixelOffset(
R.styleable.LayerDrawableItem_LayerDrawableItem_bottom, 0);
int drawableRes = a.getResourceId(
R.styleable.LayerDrawableItem_LayerDrawableItem_drawable, 0);
int id = a.getResourceId(R.styleable.LayerDrawableItem_LayerDrawableItem_id,
View.NO_ID);
a.recycle();
Drawable dr;
if (drawableRes != 0) {
dr = r.getDrawable(drawableRes);
} else {
while ((type = parser.next()) == XmlPullParser.TEXT) {
}
if (type != XmlPullParser.START_TAG) {
throw new XmlPullParserException(parser.getPositionDescription()
+ ": <item> tag requires a 'drawable' attribute or "
+ "child tag defining a drawable");
}
dr = Drawable.createFromXmlInner(r, parser, attrs);
}
addLayer(dr, id, left, top, right, bottom);
}
ensurePadding();
onStateChange(getState());
}
/**
* Add a new layer to this drawable. The new layer is identified by an id.
*
* #param layer The drawable to add as a layer.
* #param id The id of the new layer.
* #param left The left padding of the new layer.
* #param top The top padding of the new layer.
* #param right The right padding of the new layer.
* #param bottom The bottom padding of the new layer.
*/
private void addLayer(Drawable layer, int id, int left, int top, int right, int bottom) {
final LayerState st = mLayerState;
int N = st.mChildren != null ? st.mChildren.length : 0;
int i = st.mNum;
if (i >= N) {
ChildDrawable[] nu = new ChildDrawable[N + 10];
if (i > 0) {
System.arraycopy(st.mChildren, 0, nu, 0, i);
}
st.mChildren = nu;
}
mLayerState.mChildrenChangingConfigurations |= layer.getChangingConfigurations();
ChildDrawable childDrawable = new ChildDrawable();
st.mChildren[i] = childDrawable;
childDrawable.mId = id;
childDrawable.mDrawable = layer;
childDrawable.mInsetL = left;
childDrawable.mInsetT = top;
childDrawable.mInsetR = right;
childDrawable.mInsetB = bottom;
st.mNum++;
layer.setCallback(this);
}
/**
* Look for a layer with the given id, and returns its {#link Drawable}.
*
* #param id The layer ID to search for.
* #return The {#link Drawable} of the layer that has the given id in the hierarchy or null.
*/
public Drawable findDrawableByLayerId(int id) {
final ChildDrawable[] layers = mLayerState.mChildren;
for (int i = mLayerState.mNum - 1; i >= 0; i--) {
if (layers[i].mId == id) {
return layers[i].mDrawable;
}
}
return null;
}
/**
* Sets the ID of a layer.
*
* #param index The index of the layer which will received the ID.
* #param id The ID to assign to the layer.
*/
public void setId(int index, int id) {
mLayerState.mChildren[index].mId = id;
}
/**
* Returns the number of layers contained within this.
* #return The number of layers.
*/
public int getNumberOfLayers() {
return mLayerState.mNum;
}
/**
* Returns the drawable at the specified layer index.
*
* #param index The layer index of the drawable to retrieve.
*
* #return The {#link android.graphics.drawable.Drawable} at the specified layer index.
*/
public Drawable getDrawable(int index) {
return mLayerState.mChildren[index].mDrawable;
}
/**
* Returns the id of the specified layer.
*
* #param index The index of the layer.
*
* #return The id of the layer or {#link android.view.View#NO_ID} if the layer has no id.
*/
public int getId(int index) {
return mLayerState.mChildren[index].mId;
}
/**
* Sets (or replaces) the {#link Drawable} for the layer with the given id.
*
* #param id The layer ID to search for.
* #param drawable The replacement {#link Drawable}.
* #return Whether the {#link Drawable} was replaced (could return false if
* the id was not found).
*/
public boolean setDrawableByLayerId(int id, Drawable drawable) {
final ChildDrawable[] layers = mLayerState.mChildren;
for (int i = mLayerState.mNum - 1; i >= 0; i--) {
if (layers[i].mId == id) {
layers[i].mDrawable = drawable;
return true;
}
}
return false;
}
/** Specify modifiers to the bounds for the drawable[index].
left += l
top += t;
right -= r;
bottom -= b;
*/
public void setLayerInset(int index, int l, int t, int r, int b) {
ChildDrawable childDrawable = mLayerState.mChildren[index];
childDrawable.mInsetL = l;
childDrawable.mInsetT = t;
childDrawable.mInsetR = r;
childDrawable.mInsetB = b;
}
// overrides from Drawable.Callback
public void invalidateDrawable(Drawable who) {
if (mCallback != null) {
mCallback.invalidateDrawable(this);
}
}
public void scheduleDrawable(Drawable who, Runnable what, long when) {
if (mCallback != null) {
mCallback.scheduleDrawable(this, what, when);
}
}
public void unscheduleDrawable(Drawable who, Runnable what) {
if (mCallback != null) {
mCallback.unscheduleDrawable(this, what);
}
}
// overrides from Drawable
#Override
public void draw(Canvas canvas) {
final ChildDrawable[] array = mLayerState.mChildren;
final int N = mLayerState.mNum;
for (int i=0; i<N; i++) {
array[i].mDrawable.draw(canvas);
}
}
#Override
public int getChangingConfigurations() {
return super.getChangingConfigurations()
| mLayerState.mChangingConfigurations
| mLayerState.mChildrenChangingConfigurations;
}
#Override
public boolean getPadding(Rect padding) {
// Arbitrarily get the padding from the first image.
// Technically we should maybe do something more intelligent,
// like take the max padding of all the images.
padding.left = 0;
padding.top = 0;
padding.right = 0;
padding.bottom = 0;
final ChildDrawable[] array = mLayerState.mChildren;
final int N = mLayerState.mNum;
for (int i=0; i<N; i++) {
reapplyPadding(i, array[i]);
padding.left += mPaddingL[i];
padding.top += mPaddingT[i];
padding.right += mPaddingR[i];
padding.bottom += mPaddingB[i];
}
return true;
}
#Override
public boolean setVisible(boolean visible, boolean restart) {
boolean changed = super.setVisible(visible, restart);
final ChildDrawable[] array = mLayerState.mChildren;
final int N = mLayerState.mNum;
for (int i=0; i<N; i++) {
array[i].mDrawable.setVisible(visible, restart);
}
return changed;
}
#Override
public void setDither(boolean dither) {
final ChildDrawable[] array = mLayerState.mChildren;
final int N = mLayerState.mNum;
for (int i=0; i<N; i++) {
array[i].mDrawable.setDither(dither);
}
}
#Override
public void setAlpha(int alpha) {
final ChildDrawable[] array = mLayerState.mChildren;
final int N = mLayerState.mNum;
for (int i=0; i<N; i++) {
array[i].mDrawable.setAlpha(alpha);
}
}
#Override
public void setColorFilter(ColorFilter cf) {
final ChildDrawable[] array = mLayerState.mChildren;
final int N = mLayerState.mNum;
for (int i=0; i<N; i++) {
array[i].mDrawable.setColorFilter(cf);
}
}
#Override
public int getOpacity() {
return mLayerState.getOpacity();
}
#Override
public boolean isStateful() {
return mLayerState.isStateful();
}
#Override
protected boolean onStateChange(int[] state) {
final ChildDrawable[] array = mLayerState.mChildren;
final int N = mLayerState.mNum;
boolean paddingChanged = false;
boolean changed = false;
for (int i=0; i<N; i++) {
final ChildDrawable r = array[i];
if (r.mDrawable.setState(state)) {
changed = true;
}
if (reapplyPadding(i, r)) {
paddingChanged = true;
}
}
if (paddingChanged) {
onBoundsChange(getBounds());
}
return changed;
}
#Override
protected boolean onLevelChange(int level) {
final ChildDrawable[] array = mLayerState.mChildren;
final int N = mLayerState.mNum;
boolean paddingChanged = false;
boolean changed = false;
for (int i=0; i<N; i++) {
final ChildDrawable r = array[i];
if (r.mDrawable.setLevel(level)) {
changed = true;
}
if (reapplyPadding(i, r)) {
paddingChanged = true;
}
}
if (paddingChanged) {
onBoundsChange(getBounds());
}
return changed;
}
#Override
protected void onBoundsChange(Rect bounds) {
final ChildDrawable[] array = mLayerState.mChildren;
final int N = mLayerState.mNum;
int padL=0, padT=0, padR=0, padB=0;
for (int i=0; i<N; i++) {
final ChildDrawable r = array[i];
r.mDrawable.setBounds(bounds.left + r.mInsetL + padL,
bounds.top + r.mInsetT + padT,
bounds.right - r.mInsetR - padR,
bounds.bottom - r.mInsetB - padB);
padL += mPaddingL[i];
padR += mPaddingR[i];
padT += mPaddingT[i];
padB += mPaddingB[i];
}
}
#Override
public int getIntrinsicWidth() {
int width = -1;
final ChildDrawable[] array = mLayerState.mChildren;
final int N = mLayerState.mNum;
int padL=0, padR=0;
for (int i=0; i<N; i++) {
final ChildDrawable r = array[i];
int w = r.mDrawable.getIntrinsicWidth()
+ r.mInsetL + r.mInsetR + padL + padR;
if (w > width) {
width = w;
}
padL += mPaddingL[i];
padR += mPaddingR[i];
}
return width;
}
#Override
public int getIntrinsicHeight() {
int height = -1;
final ChildDrawable[] array = mLayerState.mChildren;
final int N = mLayerState.mNum;
int padT=0, padB=0;
for (int i=0; i<N; i++) {
final ChildDrawable r = array[i];
int h = r.mDrawable.getIntrinsicHeight() + r.mInsetT + r.mInsetB + + padT + padB;
if (h > height) {
height = h;
}
padT += mPaddingT[i];
padB += mPaddingB[i];
}
return height;
}
private boolean reapplyPadding(int i, ChildDrawable r) {
final Rect rect = mTmpRect;
r.mDrawable.getPadding(rect);
if (rect.left != mPaddingL[i] || rect.top != mPaddingT[i] ||
rect.right != mPaddingR[i] || rect.bottom != mPaddingB[i]) {
mPaddingL[i] = rect.left;
mPaddingT[i] = rect.top;
mPaddingR[i] = rect.right;
mPaddingB[i] = rect.bottom;
return true;
}
return false;
}
private void ensurePadding() {
final int N = mLayerState.mNum;
if (mPaddingL != null && mPaddingL.length >= N) {
return;
}
mPaddingL = new int[N];
mPaddingT = new int[N];
mPaddingR = new int[N];
mPaddingB = new int[N];
}
#Override
public ConstantState getConstantState() {
if (mLayerState.canConstantState()) {
mLayerState.mChangingConfigurations = super.getChangingConfigurations();
return mLayerState;
}
return null;
}
#Override
public Drawable mutate() {
if (!mMutated && super.mutate() == this) {
final ChildDrawable[] array = mLayerState.mChildren;
final int N = mLayerState.mNum;
for (int i = 0; i < N; i++) {
array[i].mDrawable.mutate();
}
mMutated = true;
}
return this;
}
static class ChildDrawable {
public Drawable mDrawable;
public int mInsetL, mInsetT, mInsetR, mInsetB;
public int mId;
}
static class LayerState extends ConstantState {
int mNum;
ChildDrawable[] mChildren;
int mChangingConfigurations;
int mChildrenChangingConfigurations;
private boolean mHaveOpacity = false;
private int mOpacity;
private boolean mHaveStateful = false;
private boolean mStateful;
private boolean mCheckedConstantState;
private boolean mCanConstantState;
LayerState(LayerState orig, LayerDrawable owner, Resources res) {
if (orig != null) {
final ChildDrawable[] origChildDrawable = orig.mChildren;
final int N = orig.mNum;
mNum = N;
mChildren = new ChildDrawable[N];
mChangingConfigurations = orig.mChangingConfigurations;
mChildrenChangingConfigurations = orig.mChildrenChangingConfigurations;
for (int i = 0; i < N; i++) {
final ChildDrawable r = mChildren[i] = new ChildDrawable();
final ChildDrawable or = origChildDrawable[i];
if (res != null) {
r.mDrawable = or.mDrawable.getConstantState().newDrawable(res);
} else {
r.mDrawable = or.mDrawable.getConstantState().newDrawable();
}
r.mDrawable.setCallback(owner);
r.mInsetL = or.mInsetL;
r.mInsetT = or.mInsetT;
r.mInsetR = or.mInsetR;
r.mInsetB = or.mInsetB;
r.mId = or.mId;
}
mHaveOpacity = orig.mHaveOpacity;
mOpacity = orig.mOpacity;
mHaveStateful = orig.mHaveStateful;
mStateful = orig.mStateful;
mCheckedConstantState = mCanConstantState = true;
} else {
mNum = 0;
mChildren = null;
}
}
#Override
public Drawable newDrawable() {
return new LayerDrawable(this, null);
}
#Override
public Drawable newDrawable(Resources res) {
return new LayerDrawable(this, res);
}
#Override
public int getChangingConfigurations() {
return mChangingConfigurations;
}
public final int getOpacity() {
if (mHaveOpacity) {
return mOpacity;
}
final int N = mNum;
int op = N > 0 ? mChildren[0].mDrawable.getOpacity() : PixelFormat.TRANSPARENT;
for (int i = 1; i < N; i++) {
op = Drawable.resolveOpacity(op, mChildren[i].mDrawable.getOpacity());
}
mOpacity = op;
mHaveOpacity = true;
return op;
}
public final boolean isStateful() {
if (mHaveStateful) {
return mStateful;
}
boolean stateful = false;
final int N = mNum;
for (int i = 0; i < N; i++) {
if (mChildren[i].mDrawable.isStateful()) {
stateful = true;
break;
}
}
mStateful = stateful;
mHaveStateful = true;
return stateful;
}
public synchronized boolean canConstantState() {
if (!mCheckedConstantState && mChildren != null) {
mCanConstantState = true;
final int N = mNum;
for (int i=0; i<N; i++) {
if (mChildren[i].mDrawable.getConstantState() == null) {
mCanConstantState = false;
break;
}
}
mCheckedConstantState = true;
}
return mCanConstantState;
}
}
}
I have this in values/attrs.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="LayerDrawableItem">
<attr name="LayerDrawableItem_left" format="dimension" />
<attr name="LayerDrawableItem_top" format="dimension" />
<attr name="LayerDrawableItem_right" format="dimension" />
<attr name="LayerDrawableItem_bottom" format="dimension" />
<attr name="LayerDrawableItem_drawable" format="dimension" />
<attr name="LayerDrawableItem_id" format="dimension" />
</declare-styleable>
</resources>
You need to add an mCallback member. Since you are a callback, perhaps mCallback is really this?