How can I draw a circle on highlight point in line chart? - android

I'm using mpchart to draw my charts.I wanted to increase the circle size of intersection point of highlighter and line dataset. How can I achieve this?
I read somewhere that we can add another dataset with highlighted point and increase its circle size. Is that really a good approach if my highlighter will be dragged back and forth and I'll have to update the new dataset very frequently?

When using the MpChart Library, the library contains a MarkerView class that helps us to insert the markers for displaying the selected value in the chart. We can use this MarkerView class to display any kind of view for the selected chart data.
So for the dot I created a new ChartMarker class and extended MarkerView class. Then in the constructor I passed the layout containing an image view with the dot as the src to the super.
public ChartMarker(Context context) {
//the super will take care of displaying the layout
super(context, R.layout.layout_dot);
}
Finally set the ChartMarker instance to the chart through chart.setMarkerView()
ChartMarker elevationMarker = new ChartMarker(getActivity());
elevationChart.setMarkerView(elevationMarker);
And for the layout_dot.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<ImageView
android:background="#drawable/dot"
android:layout_width="5dp"
android:layout_height="5dp" />
</LinearLayout>

I find ultimate solution.another question only can draw one point of one dataset in linechart,if I have two dataset like below,it will not draw two point。
Below code is my solution:
(it should edit the library source code,so pls import as module)
1、in LineChartRenderer class
private final Paint mHighlightPointStokePaint = new Paint();
private final Paint mHighlightPointInnerPaint = new Paint();
#Override
public void drawHighlighted(Canvas c, Highlight[] indices) {
LineData lineData = mChart.getLineData();
int entryIndex = -1;
for (Highlight high : indices) {
ILineDataSet set = lineData.getDataSetByIndex(high.getDataSetIndex());
if (set == null || !set.isHighlightEnabled())
continue;
Entry e = set.getEntryForXValue(high.getX(), high.getY());
if (!isInBoundsX(e, set))
continue;
entryIndex = set.getEntryIndex(e);
MPPointD pix = mChart.getTransformer(set.getAxisDependency()).getPixelForValues(e.getX(), e.getY() * mAnimator
.getPhaseY());
high.setDraw((float) pix.x, (float) pix.y);
// draw the lines
drawHighlightLines(c, (float) pix.x, (float) pix.y, set);
}
if (entryIndex < 0) {
return;
}
if (mChart instanceof BarLineChartBase) {
BarLineChartBase chart = (BarLineChartBase) this.mChart;
if (chart.isDrawHighlightPoint()) {
mHighlightPointInnerPaint.reset();
mHighlightPointInnerPaint.setAntiAlias(true);
mHighlightPointInnerPaint.setStyle(Paint.Style.FILL);
//交点外圈白环
mHighlightPointStokePaint.reset();
mHighlightPointStokePaint.setAntiAlias(true);
mHighlightPointStokePaint.setStyle(Paint.Style.STROKE);
mHighlightPointStokePaint.setStrokeWidth(chart.getHighLightPointStrokeWidth());
mHighlightPointStokePaint.setColor(Color.WHITE);
List<ILineDataSet> dataSets = lineData.getDataSets();
for (ILineDataSet set : dataSets) {
if (entryIndex < set.getEntryCount()) {
Entry e = set.getEntryForIndex(entryIndex);
MPPointD pix = mChart.getTransformer(set.getAxisDependency())
.getPixelForValues(e.getX(), e.getY() * mAnimator.getPhaseY());
drawHighlightPoint(c, (float) pix.x, (float) pix.y, chart, set);
}
}
}
}
}
private void drawHighlightPoint(Canvas c, float x, float y, BarLineChartBase chart, ILineDataSet set) {
//点内圆的颜色和图表线条一致,且将颜色的不透明度调满!
mHighlightPointInnerPaint.setColor(((255) << 24) | set.getColor());
//绘制内圆
c.drawCircle(x, y, chart.getHighLightPointInnerRadius(), mHighlightPointInnerPaint);
//绘制外圆
c.drawCircle(x, y, chart.getHighLightPointInnerRadius(), mHighlightPointStokePaint);
}
2、in BarLineChartBase class
/**
* flag that indicates if draw highlight point is enabled or not
*/
protected boolean isDrawHighlightPoint = false;
/**
* the highlight point inner radius (px)
*/
protected int mHighLightPointInnerRadius = 1;
/**
* the highlight point stroke width (px)
*/
protected int mHighLightPointStrokeWidth = 1;
public boolean isDrawHighlightPoint() {
return isDrawHighlightPoint;
}
public void setDrawHighlightPoint(boolean drawHighlightPoint) {
isDrawHighlightPoint = drawHighlightPoint;
}
public int getHighLightPointInnerRadius() {
return mHighLightPointInnerRadius;
}
public void setHighLightPointInnerRadius(int mHighLightPointStrokeWidth) {
this.mHighLightPointInnerRadius = mHighLightPointStrokeWidth;
}
public int getHighLightPointStrokeWidth() {
return mHighLightPointStrokeWidth;
}
public void setHighLightPointStrokeWidth(int mHighLightPointStrokeWidth) {
this.mHighLightPointStrokeWidth = mHighLightPointStrokeWidth;
}
3、apply to your chart
lineChart.setDrawHighlightPoint(true);
lineChart.setHighLightPointInnerRadius(Utils.dp2px(5, context));
lineChart.setHighLightPointStrokeWidth(Utils.dp2px(3, context));

Related

MPAndroidChart - How to display text to the left of LimitLine?

I'm trying to display text on the left of the LimitLine like this:
However these are the only options I'm getting for setting the position of the Label for limit line.
I'm using LimitLine.LimitLabelPosition.LEFT_TOP and it only displays the Label above the Limit line.
YAxis leftAxis = mChart.getAxisLeft();
LimitLine minimumLimit = new LimitLine(50f, "Minimum Limit");
minimumLimit.setLineWidth(0.5f);
minimumLimit.setTextColor(ContextCompat.getColor(this, R.color.white_60_opacity));
minimumLimit.setLabelPosition(LimitLine.LimitLabelPosition.LEFT_TOP);
leftAxis.addLimitLine(minimumLimit);
How do I display the LimitLine's Label to the left of the LimitLine?
Edit:
I have also tried used the methods .setXOffset(50f) and .setYOffset(50f) but this only shifts the position of the label and not the line minimumLimit.
You can achieve this by using a custom YAxisRenderer with a little modification of the override method public void renderLimitLines(Canvas c).
The modifications needed for this purpose are:
1.To calculate the label width of each limit line to be able to move the limit line to the correct x position like below:
limitLinePath.moveTo(mViewPortHandler.contentLeft()+getLabelTextWidth(l), pts[1]);
2.To draw the label to the new x,y position something like this:
c.drawText(label, mViewPortHandler.contentLeft() + xOffset, pts[1]+l.getYOffset(), mLimitLinePaint);
Below is a custom MyYAxisRenderer containing the above modifications:
public class MyYAxisRenderer extends YAxisRenderer {
private final Paint textPaint;
public MyYAxisRenderer(ViewPortHandler viewPortHandler, YAxis yAxis, Transformer trans) {
super(viewPortHandler, yAxis, trans);
textPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
}
#Override
public void renderLimitLines(Canvas c) {
List<LimitLine> limitLines = mYAxis.getLimitLines();
if (limitLines == null || limitLines.size() <= 0)
return;
float[] pts = mRenderLimitLinesBuffer;
pts[0] = 0;
pts[1] = 0;
Path limitLinePath = mRenderLimitLines;
limitLinePath.reset();
for (int i = 0; i < limitLines.size(); i++) {
LimitLine l = limitLines.get(i);
if (!l.isEnabled())
continue;
int clipRestoreCount = c.save();
mLimitLineClippingRect.set(mViewPortHandler.getContentRect());
mLimitLineClippingRect.inset(0.f, -l.getLineWidth());
c.clipRect(mLimitLineClippingRect);
mLimitLinePaint.setStyle(Paint.Style.STROKE);
mLimitLinePaint.setColor(l.getLineColor());
mLimitLinePaint.setStrokeWidth(l.getLineWidth());
mLimitLinePaint.setPathEffect(l.getDashPathEffect());
pts[1] = l.getLimit();
mTrans.pointValuesToPixel(pts);
limitLinePath.moveTo(mViewPortHandler.contentLeft()+getLabelTextWidth(l), pts[1]);
limitLinePath.lineTo(mViewPortHandler.contentRight(), pts[1]);
c.drawPath(limitLinePath, mLimitLinePaint);
limitLinePath.reset();
String label = l.getLabel();
// if drawing the limit-value label is enabled
if (label != null && !label.equals("")) {
mLimitLinePaint.setStyle(l.getTextStyle());
mLimitLinePaint.setPathEffect(null);
mLimitLinePaint.setColor(l.getTextColor());
mLimitLinePaint.setTypeface(l.getTypeface());
mLimitLinePaint.setStrokeWidth(0.5f);
mLimitLinePaint.setTextSize(l.getTextSize());
final float labelLineHeight = Utils.calcTextHeight(mLimitLinePaint, label);
float xOffset = getLimitLineXOffset(l);
float yOffset = l.getLineWidth() + labelLineHeight + l.getYOffset();
final LimitLine.LimitLabelPosition position = l.getLabelPosition();
//draw the label on the left in the same y position of the limit line
mLimitLinePaint.setTextAlign(Paint.Align.LEFT);
c.drawText(label,
mViewPortHandler.contentLeft() + xOffset,
pts[1]+l.getYOffset(), mLimitLinePaint);
}
c.restoreToCount(clipRestoreCount);
}
}
private float getLimitLineXOffset(LimitLine l){
return Utils.convertDpToPixel(4f) + l.getXOffset();
}
private float getLabelTextWidth(LimitLine l) {
String label = l.getLabel();
if (label != null && !label.equals("")) {
textPaint.setStyle(l.getTextStyle());
textPaint.setPathEffect(null);
textPaint.setColor(l.getTextColor());
textPaint.setTypeface(l.getTypeface());
textPaint.setStrokeWidth(0.5f);
textPaint.setTextSize(l.getTextSize());
int textWidth = Utils.calcTextWidth(textPaint, label);
float xOffset = getLimitLineXOffset(l);
return textWidth + (xOffset*2);
}
return 0;
}
}
In the above renderer i have added two helper functions one for the calculation of the label text width private float getLabelTextWidth(LimitLine l) for a specific limit line and one to get the x offset of each limit line private float getLimitLineXOffset(LimitLine l) which you can modify based on your needs.
And you can use the above Renderer like the below:
lineChart.setRendererLeftYAxis(new MyYAxisRenderer(lineChart.getViewPortHandler(), lineChart.getAxisLeft(), lineChart.getTransformer(YAxis.AxisDependency.LEFT)));
Result:
Note: This was tested with v3.1.0 ('com.github.PhilJay:MPAndroidChart:v3.1.0')

How to place the text values inside in MPAndroidChart circle?

I am developing a chart based application, I am using MPAndroidChart library, I need to place the text value inside of circle, i tried to display,Thanks for if any suggestions related this,
i attached a screenshot related to that issue. I need to be do like this
but i get like this image:
Thanks Again for helping this issue,
ArrayList<Entry> e1 = new ArrayList<Entry>();
float[] values = new float[]{48, 59, 79, 29, 39, 50, 60};
for (int i = 0; i < values.length; i++) {
e1.add(new Entry(values[i], i, "line3"));
}
int[] color = {Color.parseColor("#D13385"), Color.parseColor("#37D04E"), Color.parseColor("#33D1D1"), Color.parseColor("#D1C933")};
LineDataSet d1 = new LineDataSet(e1, "" + cnt);
d1.setColors(color);
d1.setLineWidth(3.0f);
d1.setCircleSize(7.0f);
d1.setDrawValues(true);
d1.setCircleColor(Color.parseColor("#891e9a"));
d1.setCircleColorHole(Color.parseColor("#891e9a"));
d1.setDrawHighlightIndicators(false);
d1.setDrawFilled(false);
d1.setFillAlpha(20);
d1.setHighlightLineWidth(50f);
d1.setValueTextSize(10f);
Currently it is not possible to change the position where the values are drawn by default. You will have to modify the library to get that behaviour.
It is a bit "hacky", but I've managed to achieve such layout You've provided by creating two sets of data and attaching them to same chart. One set (lets call it "dots") contains your data needed to be displayed as dots. The second one ("lines") is a bit offset downwards (y value minus some experimentally picked value). Now you can set no line displaying for "lines" and a lines for "dots", no values labels for "dots" and white labels for "lines" and by experimentally moving your y values back and forth you can achieve overlaying values labels from one chart on top of another ("dots" will be covered by "lines" values).
UPDATE:
Actually, I've an answer more elegant, that I've provided! Use Highlight[] and create array of highlights.
highlihts = new Highlight[values_dots.size()];
for (int i = 0; i < values_dots.size(); i++) {
Highlight h = new Highlight(values_dots.get(i).getX(),values_dots.get(i).getY(), 0);
highlihts[i] = h;
}
chart.highlightValues(highlihts);
In CustomMarkerView class position marker like so:
#Override
public MPPointF getOffset() {
return new MPPointF(-(getWidth() / 2), -(getHeight() / 2));
}
Boom
P.S. Philipp Jahoda, awesome library!
there was an easy way to do this
custom maker view
Marker view
public class MyMarkerView extends MarkerView {
private final TextView tvContent;
public MyMarkerView(Context context, int layoutResource) {
super(context, layoutResource);
tvContent = findViewById(R.id.tvContent);
}
// runs every time the MarkerView is redrawn, can be used to update the
// content (user-interface)
#SuppressLint("SetTextI18n")
#Override
public void refreshContent(Entry e, Highlight highlight) {
if (e instanceof CandleEntry) {
CandleEntry ce = (CandleEntry) e;
tvContent.setText(Utils.formatNumber(ce.getHigh(), 0, true)+(ce.getData()));
} else {
tvContent.setText(Utils.formatNumber(e.getY(), 0, true)+"\n "+(e.getData()));
}
super.refreshContent(e, highlight);
}
#Override
public MPPointF getOffset() {
return new MPPointF(-(getWidth() / 2), -getHeight());
}
}
and lastly in your activity
values.add(new Entry(i, val,"Custom message per value"));
Preview be like
example screen shot
there was an easy way to do this
custom maker view
<TextView
android:id="#+id/tvContent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_marginTop="7dp"
android:layout_marginLeft="5dp"
android:layout_marginRight="5dp"
android:text=""
android:textSize="12sp"
android:textColor="#android:color/white"
android:ellipsize="end"
android:singleLine="true"
android:textAppearance="?android:attr/textAppearanceSmall" />
Marker view class
public class MyMarkerView extends MarkerView {
private final TextView tvContent;
public MyMarkerView(Context context, int layoutResource) {
super(context, layoutResource);
tvContent = findViewById(R.id.tvContent);
}
// runs every time the MarkerView is redrawn, can be used to update the
// content (user-interface)
#SuppressLint("SetTextI18n")
#Override
public void refreshContent(Entry e, Highlight highlight) {
if (e instanceof CandleEntry) {
CandleEntry ce = (CandleEntry) e;
tvContent.setText(Utils.formatNumber(ce.getHigh(), 0, true)+(ce.getData()));
} else {
tvContent.setText(Utils.formatNumber(e.getY(), 0, true)+"\n "+(e.getData()));
}
super.refreshContent(e, highlight);
}
#Override
public MPPointF getOffset() {
return new MPPointF(-(getWidth() / 2), -getHeight());
}
}
and lastly in your activity
values.add(new Entry(i, val,"Custom message per value"));
Preview be like
example screen shot
There are two possibilities:
(1) Not so good: Shift the y-value of the label position
Two Data sets one for text and one for the line (including circles)
Modifiy the y-position for the text value with a constant offset
Pro: Easy
Con: The offset is not always constant (see offset is not always similar)
(2) Better: Override the drawValues method from LineChartRenderer
In LineChartRenderer.java -> drawValues the text is vertically shifted by this line:
drawValue(c, formatter.getPointLabel(entry), x, y - valOffset, dataSet.getValueTextColor(j / 2));
So to get rid of the "- valOffset":
1.Override the drawValues method
Create a new java file "CenteredTextLineChartRenderer.java" and override method drawValues from LineChartRenderer
2.Modify the y-valOffset to y+textHeight*0.35f
Add float textHeight = dataSet.getValueTextSize();
public class CenteredTextLineChartRenderer extends LineChartRenderer {
public CenteredTextLineChartRenderer(LineDataProvider chart, ChartAnimator animator, ViewPortHandler viewPortHandler) {
super(chart, animator, viewPortHandler);
}
//Modified drawValues Method
// Center label on coordinate instead of applying a valOffset
#Override
public void drawValues(Canvas c) {
if (isDrawingValuesAllowed(mChart)) {
List<ILineDataSet> dataSets = mChart.getLineData().getDataSets();
for (int i = 0; i < dataSets.size(); i++) {
ILineDataSet dataSet = dataSets.get(i);
float textHeight = dataSet.getValueTextSize();
if (!shouldDrawValues(dataSet) || dataSet.getEntryCount() < 1)
continue;
// apply the text-styling defined by the DataSet
applyValueTextStyle(dataSet);
Transformer trans = mChart.getTransformer(dataSet.getAxisDependency());
// make sure the values do not interfear with the circles
int valOffset = (int) (dataSet.getCircleRadius() * 1.75f);
if (!dataSet.isDrawCirclesEnabled())
valOffset = valOffset / 2;
mXBounds.set(mChart, dataSet);
float[] positions = trans.generateTransformedValuesLine(dataSet, mAnimator.getPhaseX(), mAnimator
.getPhaseY(), mXBounds.min, mXBounds.max);
ValueFormatter formatter = dataSet.getValueFormatter();
MPPointF iconsOffset = MPPointF.getInstance(dataSet.getIconsOffset());
iconsOffset.x = Utils.convertDpToPixel(iconsOffset.x);
iconsOffset.y = Utils.convertDpToPixel(iconsOffset.y);
for (int j = 0; j < positions.length; j += 2) {
float x = positions[j];
float y = positions[j + 1];
if (!mViewPortHandler.isInBoundsRight(x))
break;
if (!mViewPortHandler.isInBoundsLeft(x) || !mViewPortHandler.isInBoundsY(y))
continue;
Entry entry = dataSet.getEntryForIndex(j / 2 + mXBounds.min);
if (dataSet.isDrawValuesEnabled()) {
//drawValue(c, formatter.getPointLabel(entry), x, y - valOffset, dataSet.getValueTextColor(j / 2));
drawValue(c, formatter.getPointLabel(entry), x, y+textHeight*0.35f, dataSet.getValueTextColor(j / 2));
}
if (entry.getIcon() != null && dataSet.isDrawIconsEnabled()) {
Drawable icon = entry.getIcon();
Utils.drawImage(
c,
icon,
(int)(x + iconsOffset.x),
(int)(y + iconsOffset.y),
icon.getIntrinsicWidth(),
icon.getIntrinsicHeight());
}
}
MPPointF.recycleInstance(iconsOffset);
}
}
}
}
3.Set your own LineChart renderer to your modified drawValues class
LineChart mChart = (LineChart) mainActivity.findViewById(R.id.LineChart);
mChart.setRenderer(new CenteredTextLineChartRenderer(mChart,mChart.getAnimator(),mChart.getViewPortHandler()));
Run your code and manually adapt the 0.35f offset in your CenteredTextLineChartRenderer class
Now your text is always vertically centered!
IMPORTANT: With deleting the valOffset your label is not vertically centered as the text anchor is not in the center of your text label. So you have to insert a manual offset "textHeight*0.35f" (just try it out). But the big advantage of method (2) is that the text is always centered with the same offset also for example in landscape mode and on other screen sizes...

Android TransitionDrawable not fading

I have an ImageView and I am trying to fade from one image to the next using this code:
Drawable bgs[] = new Drawable[2];
public void redraw(int[][] grid) {
bgs[0] = bgs[1];
bgs[1] = new GameDrawable(grid, prefs.colors);
if (bgs[0] == null) {
gameField.setImageDrawable(bgs[1]);
} else {
TransitionDrawable crossfader = new TransitionDrawable(bgs);
crossfader.setCrossFadeEnabled(true);
gameField.setImageDrawable(crossfader);
crossfader.startTransition(500);
}
}
gameField is correctly referenced as an ImageView.
gameDrawable simply extends Drawable and draws the grid.
On each move and action the new GameDrawable is being rendered correctly but there is no fading whatsoever. The new image is simply displayed instantaneously. I have tried lengthening the transition time and swapping the order of the drawables with no effect.
Any help on is appreciated.
Update: I have now set my transition to something ridiculously long like 500000. The first drawable shows for a few seconds and then suddenly the second drawable appears. So still no transition.
Update 2:
I think my Drawable might be implemented incorrectly, so I have attached the code.
public class GameDrawable extends Drawable {
private Paint paint = new Paint();
private float blockWidth = 1;
private int[][] myGrid;
private int myColor;
private List<Point> myPoints;
public GameDrawable(int[][] grid) {
super();
this.myGrid = grid;
this.myColor = colors[yourColor];
paint.setStrokeWidth(1);
paint.setStyle(Paint.Style.FILL);
paint.setAlpha(0);
this.myPoints = yourPoints;
}
#Override
public void draw(Canvas canvas) {
float height = getBounds().height();
float width = getBounds().width();
blockWidth = width / myGrid.length;
if (height / myGrid.length < blockWidth) {
blockWidth = height / myGrid.length;
}
for (int x = 0; x < myGrid.length; x++) {
for (int y = 0; y < myGrid[x].length; y++) {
paint.setColor(colors[myGrid[x][y]]);
canvas.drawRect(x * blockWidth, y * blockWidth, (x+1)*blockWidth, (y+1)*blockWidth, paint);
}
}
}
#Override
public void setAlpha(int alpha) {
paint.setAlpha(alpha);
invalidateSelf();
}
#Override
public void setColorFilter(ColorFilter cf) {
paint.setColorFilter(cf);
invalidateSelf();
}
#Override
public int getOpacity() {
return PixelFormat.TRANSLUCENT;
}
}
Looking at your code, I see a problem at the line
bgs[0] = bgs[1];
bgs[1] has not yet been defined before this line and so bgs[0] is null for the first method call. Because of this, (bgs[0] == null) is true, and so the later defined bgs[1] is directly set to the gameField ImageView.
Use corrected code below.
Drawable bgs[] = new Drawable[2];
Drawable firstDrawable = getResources().getDrawable(R.drawable.transparent);
public void redraw(int[][] grid) {
bgs[0] = firstDrawable;
bgs[1] = new GameDrawable(grid, prefs.colors);
firstDrawable = bgs[1];
TransitionDrawable crossfader = new TransitionDrawable(bgs);
crossfader.setCrossFadeEnabled(true);
gameField.setImageDrawable(crossfader);
crossfader.startTransition(500);
}
Note that TransitionDrawable does not work properly when the Drawable sizes are different. So you may need to resize firstDrawable beforehand.
EXTRA: I would avoid setCrossFadeEnabled(true) since the whole TransitionDrawable becomes translucent during the transition, revealing the background. Sometimes, this creates a "blinking" effect and destroys the smoothness of the transition.
EDIT: Looking at your custom Drawable implementation, I think the problem lies in the line
canvas.drawColor(Color.WHITE);
in the draw() method.
I looked at TransitionDrawable.java source and found that setAlpha is called on the drawables to get the cross fade effect. However, your canvas has a solid white color and setAlpha() only affects the paint. Hope this is your answer.
EDIT 2: The actual problem, as pointed out by Michael, was that TransitionDrawable's setAlpha() calls on the Drawables were rendered ineffective due to paint.setColor() in the GameDrawable's draw() method overriding the paint's alpha value set by the TransitionDrawable.

Looking for equivalent to UIElement.RenderTransform to visualize level of deviation in Android

We are currently building an app with Xamarin in which we want to visualize the level of deviation from a neutral value. The current state will be updated every 80 to 150 milliseconds.
Basically we're looking for something like this:
---- ´´´´´ ---- (deviation to positive)
---- ----- ---- (no deviation)
---- ___ ---- (deviation to negative)
The lines on the outsides are going to remain in the same position while the line in the middle is moving up and down to show the current deviation.
For WP it is as easy as drawing 3 thin Rectangles in a line and translating values on a Y-Axis to the middle Rectangle via Rectangle.RenderTransform.
In Android we use LevelListDrawable to tie several png's to a certain value range and set the current level from code:
This is the LevelList.xml:
<?xml version="1.0" encoding="utf-8"?>
<level-list xmlns:android="http://schemas.android.com/apk/res/android" >
<item
android:drawable="#drawable/down"
android:minLevel="0"
android:maxLevel="33" />
<item
android:drawable="#drawable/neutral"
android:minLevel="34"
android:maxLevel="66" />
<item
android:drawable="#drawable/top"
android:minLevel="67"
android:maxLevel="100" />
</level-list>
down, neutral and top refer to a .png showing the current state. (There are going to be about 20 different states)
LevelList.xml is set as ImageDrawable for the ImageView which is used to display the current state:
_graphicsView = FindViewById<ImageView>(Resource.Id.GraphicsView);
var drawable = Resources.GetDrawable(Resource.Drawable.LevelList);
_graphicsView.SetImageDrawable(drawable);
This is the update method in the MainActivity:
public void UpdateDeviation(int deviation)
{
_graphicsView.SetImageLevel(deviation);
}
Our current approach is working but we're not happy about having to store a multitude of png's for every state. Can you think of an better solution for Android with regard to update speed and APK size?
You can definitely improve this by implementing your own SurfaceView and rendering a path based the different state.
I was bored so I wrote an example:
Code:
public enum LineState
{
Up, Middle, Down,
}
public class OscillatingSurface : SurfaceView
{
public LineState _state = LineState.Middle;
private readonly Paint _paint;
public OscillatingSurface (Context context)
: base(context)
{
_paint = new Paint(PaintFlags.AntiAlias);
_paint.SetStyle (Paint.Style.Stroke);
_paint.Color = Color.White;
_paint.StrokeWidth = 10.0f;
}
public void SetState(LineState state)
{
this._state = state;
float factor = 0.5f;
if (_state == LineState.Up) {
factor = 0.6f;
} else if (state == LineState.Down) {
factor = 0.4f;
}
float width = (float)this.Width;
float height = (float)this.Height;
Path path = new Path();
path.MoveTo (0.0f, height * 0.5f); // Far left point.
path.LineTo (width * 0.25f, height * 0.5f); // Middle left point.
path.LineTo (width * 0.25f, height * factor); // Left Oscilation point
path.LineTo (width * 0.75f, height * factor); // Right oscilation point
path.LineTo (width * 0.75f, height * 0.5f); // Middle right point.
path.LineTo (width, height * 0.5f); // Far right point.
var canvas = this.Holder.LockCanvas (); // Acquire surface.
canvas.DrawColor (Color.Black); // Clear surface.
canvas.DrawPath (path, _paint); // Render the path.
Holder.UnlockCanvasAndPost (canvas); // Release and render!
}
}
[Activity (Label = "Oscillating Surface", MainLauncher = true, Icon = "#drawable/icon")]
public class MainActivity : Activity
{
OscillatingSurface surface;
Oscillator oscillator;
protected override void OnCreate (Bundle bundle)
{
base.OnCreate (bundle);
surface = new OscillatingSurface (this);
oscillator = new Oscillator (this, surface, 1000);
SetContentView (surface);
}
protected override void OnResume ()
{
oscillator.Start ();
base.OnResume ();
}
}
public class Oscillator
{
int _frequency;
OscillatingSurface _surface;
Activity _owner;
public Oscillator(Activity owner, OscillatingSurface surface, int frequency)
{
_surface = surface;
_frequency = frequency;
_owner = owner;
}
public void Start()
{
System.Threading.Tasks.Task.Run (() => {
while (true) {
LineState next = (LineState)((((int)_surface._state) + 1) % 3);
_owner.RunOnUiThread( () => {
_surface.SetState(next);
});
Thread.Sleep(_frequency);
}
});
}
}
What it does:
You only really need to pay attention to the OscillatingSurface class in the above code; everything else is just for example.
Resources:
Drawable Surface Tutorial
Xamarin.Android Drawing Samples

how to swap images on canvas in android?

I have displayed images from resource in my application as rows and columns randomly.
From those rows and columns i would like to swap the two images when user click on beside of images only.The following code will display the images in rows and columns as randomly.
private void rand(int imagesList[][])
{
Random generator = new Random();
int temp;
for (int i = 0; i < MAX_ROWS; i++)
for(int j = 0; j < MAX_COLS; j++)
{
int randRowPos = generator.nextInt(MAX_ROWS);
int randColPos = generator.nextInt(MAX_COLS);
temp = imagesList[i][j];
imagesList[i][j] = imagesList[randRowPos][randColPos];
imagesList[randRowPos][randColPos]= temp;
}
}
by using the above code i have displayed images as rows and columns.
Here how can i swap the two beside images from rows and columns?
please any body help me.....
I don't have privilege to add comment, so I am posting this as answer.
What do you mean by beside images ?
Is it when user will click on one image , it should get swapped with the image next to it ?
Can you also share the code where you have binned these images to view or any adapterview ?
EDIT :
I too had similar situation at the times when absolute layouts were alive.
What I had done is as follows:
Class:
public class PlayScreen extends Activity implements OnTouchListener
private Panel mainPanel; // Panel for out display
boolean firstClick = false;
OnCreate :
main = new Panel(this);
// Display the panel (calls the ondraw and updates the display)
setContentView(main,new ViewGroup.LayoutParams(screenwidth,screenheight));
// Listen for touchevents on the panel
main.setOnTouchListener(this);
Panel :
class Panel extends View
{
/*
* Init a Panel to draw on and a paint to paint with
*/
Paint mBitmapPaint;
public Panel(Context context)
{
super(context);
mBitmapPaint = new Paint();
}
#Override
protected void onDraw(Canvas canvas)
{
drawImages(canvas);
}
}
drawImages :
private void drawImages(Canvas canvas)
{
for(int i = 0; i<MAX_ROWS; i++){
for(int j=0; j<MAX_COLS; j++)
{
int xpos = j*bmp.getWidth()+j*2;
int ypos = i*bmp.getHeight()+i*2;
bmp = BitmapFactory.decodeResource(mContext.getResources(), items[i][j],opts);
canvas.drawBitmap(bmp,xpos,ypos,mBitmapPaint);
clickzonex.add(xpos);
clickzoney.add(ypos);
clickzonei.add(i);
clickZonej.add(j);
}
}
}
OnTouch:
onTouch(View v, MotionEvent event) :
if (event.getAction() == MotionEvent.ACTION_DOWN)
{
// imply logic
x = (int) event.getX();
y = (int) event.getY();
for(int i = 0; i < clickzonex.size();i++)
{
if((x>clickzonex[i]) && (x<(clickzonex[i]+ bmp.getwidth())) && (y>(clickzoney[i])) && (y<(clickzoney[i]+bmp.getHeight())))
{
// we have a click in a zone so we get the card-number in that zone
if(firstClick == false)
{
itemAti=clickzonei[i];
itemAtj = clickzonej[i];
firstclick = false;
}
else
{
FirstItemToSwap = items[clickzonei[i]][clickzonej[i]];
SecondItemToSwap = items[itemAti][itemAtj];
// Now do the swaping using any algo you like.
main.postInvalidate();
firstclick = true;
}
break;
}
}
return true;
}
else
{
return false;
}
I have just tried to show you the logic using my own example and mixing it with your code. The main point is that in ondraw method just call drawcanvas and on touch just swap the items[][] and call postinvalidate method of Panel class.
I had to do something like this once. I just swapped the image references in the array and did a redraw(invalidate()) on the whole thing.
void swap(int x1, int y1, int x2, int y2) {
// swap items[x1][y1] and items[x2][y2]
........
invalidate();
}
Not quite sure what you are actually asking here, so please try to clarify the question.
One approach could be to use a ViewAnimator as a parent for each of the drawable ImageViews.
<ViewAnimator
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/slideshow_animator"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
Then once you capture the event that should trigger the swap, you can use the ViewAnimator to swap the View (in your case ImageView) it uses. You can even easily add an animation effect
http://developer.android.com/reference/android/widget/ViewAnimator.html

Categories

Resources