Android Custom HorizontalScrollView child disappers when it is recreated - android

In a project of mine, I have a custom HorizontalScrollView class that displays a line chart. It gets rid of all of the elements and then it adds a single ImageView as a child element. It works fine the first time, but if I were to change to other fragment and switch back to the fragment containing the line chart, the ImageView is not present, although the object is recreated exactly the same way as the first time. If I inspect it with the DDMS the ImageView is really not in the UI graph. Any ideas why?? I really need some help on that.
That is my code (from which I have omitted some irrelevant parts):
public class LineChartView extends HorizontalScrollView {
// Private Constants ...
// Private Fields...
public LineChartView(Context context) {
super(context);
init();
}
public LineChartView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public LineChartView(Context context, float[] lineValues) {
super(context);
mLineValues = lineValues;
init();
}
public LineChartView(Context context, AttributeSet attrs, float[] lineValues) {
super(context, attrs);
mLineValues = lineValues;
init();
}
private void init() {
// Setting densityMultiplier
DisplayMetrics displayMetrics = getContext().getResources().getDisplayMetrics();
int pixelDensity = displayMetrics.densityDpi;
mDensityMultiplier = pixelDensity / DisplayMetrics.DENSITY_DEFAULT;
// Initalising Paints...
initChildElements();
}
private void initChildElements() {
removeAllViews();
// Adding the image view to the visual graph
mChartImage = new ImageView(getContext());
ViewGroup.LayoutParams chartLayoutParams = new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
mChartImage.setLayoutParams(chartLayoutParams);
addViewInLayout(mChartImage, 0, mChartImage.getLayoutParams(), true);
}
public void setLineValuesList(float[] lineValuesList) {
if (lineValuesList == null) {
mLineValues = new float[]{};
} else {
mLineValues = lineValuesList;
}
requestLayout();
invalidate();
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
mChartImage.setImageDrawable(renderLineChart(h, 1.5f)); // Calculate the bitmap with the same height
super.onSizeChanged(w, h, oldw, oldh);
}
private BitmapDrawable renderLineChart(int height, float scale) {
if (height <= 0 || mLineValues.length == 0)
return null;
// Drawing logic... (not really relevant)
return new BitmapDrawable(getContext().getResources(), resultBitmap);
}
private float calculateWidth(float distanceBetween) {
int strokeCount = mLineValues.length - 1;
return strokeCount * distanceBetween;
}
private float calculateDistanceToNextValue(float scale) {
return BASE_DISTANCE_BETWEEN_POINTS * mDensityMultiplier * scale;
}
}

Related

Android fragmented ProgressBar (like defrag or bittorrent)

I need an advise on optimizing a custom indicator, that shows progress of downloading file in multiple chunks, in concurrent threads. I couldn't find a correct name for that type - pieces, fragments, chunks? But it should look like bittorrent progress bar or defrag progress from Win XP. And it looks like this:
My custom ProgressBar class as following:
public class FragmentedProgressBar extends ProgressBar {
private int height;
private float fragWidth;
private final ArrayList<Integer> stateColors = new ArrayList<>();
private final Paint progressPaint = new Paint();
private ConcurrentHashMap<Integer, Integer> barData;
public FragmentedProgressBar(Context context) {
super(context);
this.init(context);
}
public FragmentedProgressBar(Context context, AttributeSet attrs) {
super(context, attrs);
this.init(context);
}
public FragmentedProgressBar(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
this.init(context);
}
#SuppressWarnings("deprecation")
private void init(Context context) {
barData = new ConcurrentHashMap<>();
stateColors.addAll(
Arrays.asList(
context.getResources().getColor(android.R.color.holo_blue_dark),
context.getResources().getColor(android.R.color.holo_green_light),
context.getResources().getColor(android.R.color.holo_orange_light),
context.getResources().getColor(android.R.color.holo_red_light)
)
);
}
public synchronized void setProgress(int progress, int state) {
/* state serves to indicate "started", "ready", "retry", "error" by color */
if(barData != null ) {
barData.put(progress, state);
}
super.setProgress(progress);
}
#Override
protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = getMeasuredWidth();
height = getMeasuredHeight();
setMeasuredDimension(width, height);
fragWidth = (float) width / getMax();
}
#Override
protected synchronized void onDraw(Canvas canvas) {
for (Map.Entry<Integer, Integer> ent : barData.entrySet()) {
int id = ent.getKey();
int state = ent.getValue();
float xleft = fragWidth * ( id - 1 );
progressPaint.setColor(stateColors.get(state));
canvas.drawRect(xleft, 0.0f, xleft + fragWidth, 0.0f + height, progressPaint);
}
}
}
However, in this approach, it redraws whole bar on every progress tick, and, I think, it's quite inefficient.
I've done formerly same bar in javafx, extending Canvas and drawing each chunk separately on it.
What will be a better solution for android, desirably extending and reusing the ProgressBar class?
Thanks

Creating a circle with clickable dots in Android

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

Custom View class not drawing itself in custom ViewGroup

The custom View in my custom ViewGroup refuses to show the drawable given to it by calling setImageResource(). It is laid out as I need it, however, as you can see in this screenshot, it's empty:
Also, it won't react on an onClick event.
Here's the java code for my Activity
public class MainActivity extends Activity {
public static final String TAG = "MainActivity";
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
BattleShipsGameBoard gb = (BattleShipsGameBoard) findViewById(R.id.gameboard);
Tile tile = new Tile(this);
tile.setImageResource(R.drawable.tile_hit);
tile.setGameObjectType(BattleShipsGameBoard.LayoutParams.LAYOUT_TYPE_TILE);
tile.setPosition(new Point(50, 50));
tile.setWidth(90);
tile.setHeight(90);
gb.addView(tile);
}
}
and my custom view
public class Tile extends ImageView {
#SuppressWarnings("unused")
private static final String TAG = "Tile";
public int tag;
public int gameObjectType;
public Point position = new Point(0, 0);
public int mWidth = 1;
public int mHeight = 1;
public boolean isSelected = false;
public Tile(Context context) {
super(context);
setLayoutParams(new BattleShipsGameBoard.LayoutParams(
BattleShipsGameBoard.LayoutParams.WRAP_CONTENT,
BattleShipsGameBoard.LayoutParams.WRAP_CONTENT));
}
public Tile(Context context, AttributeSet attrs) {
super(context, attrs);
}
public Tile(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public void confirmChangesInLayout() {
BattleShipsGameBoard.LayoutParams lp = (BattleShipsGameBoard.LayoutParams) this
.getLayoutParams();
lp.setPosition(this.position);
lp.setWidth(this.mWidth);
lp.setHeight(this.mHeight);
setLayoutParams(lp);
invalidate();
requestLayout();
}
//... getters and setters, the setters all call confirmChangesInLayout()
}
my simple custom ViewGroup:
public class BattleShipsGameBoard extends ViewGroup {
public static class LayoutParams extends MarginLayoutParams {
public LayoutParams(Context c, AttributeSet attrs) {
super(c, attrs);
}
public LayoutParams(int width, int height) {
super(width, height);
}
public Point position = new Point(0, 0);
public int type = 0;
public int height = 0;
public int width = 0;
//getters and setters
}
public BattleShipsGameBoard(Context context) {
super(context);
}
public BattleShipsGameBoard(Context context, AttributeSet attrs) {
super(context, attrs);
}
public BattleShipsGameBoard(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
private float unitWidth;
private float unitHeight;
private int parentWidth;
private int parentHeight;
/**
* count of units the screen estate is divided by
*/
public static int unitCount = 100;
/**
* Rectangle in which the size of a child is temporarily stored
*/
private Rect mTmpChildRect = new Rect();
/**
* lays out children
*/
#Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
Log.d(TAG, "-------------STARTING LAYOUT, " + getChildCount() + " children -------------");
int count = getChildCount();
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (child.getVisibility() != GONE) {
LayoutParams lp = (LayoutParams) child.getLayoutParams();
Point pos = lp.getPosition();
int height = lp.getHeight();
int width = lp.getWidth();
measureChild(child, parentWidth, parentHeight);
mTmpChildRect.left = (int) ((pos.x - (width / 2)) * unitWidth);
mTmpChildRect.right = (int) ((pos.x + (width / 2)) * unitWidth);
mTmpChildRect.top = (int) ((pos.y + (height / 2)) * unitHeight);
mTmpChildRect.bottom = (int) ((pos.y - (height / 2)) * unitHeight);
child.layout(mTmpChildRect.left, mTmpChildRect.top, mTmpChildRect.right, mTmpChildRect.bottom);
Log.d(TAG,
}
}
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
parentHeight = MeasureSpec.getSize(heightMeasureSpec);
parentWidth = MeasureSpec.getSize(widthMeasureSpec);
unitHeight = parentHeight / unitCount;
unitWidth = parentWidth / unitCount;
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
if (child.getVisibility() != View.GONE) {
child.measure(widthMeasureSpec, heightMeasureSpec);
}
}
setMeasuredDimension(parentWidth, parentHeight);
}
/**
* Any layout manager that doesn't scroll will want this.
*/
#Override
public boolean shouldDelayChildPressedState() {
return false;
}
}
I just found the problem.
In the onLayout() method I mixed up mTmpChildRect.top and mTmpChildRect.bottom which is why it looked like it was laid out correctly but nothing could be drawn.

Android - Change padding for viewpager side pages

I need to create the following layout to my viewpager:
I'm using PageContainer:
public class PagerContainer extends FrameLayout implements ViewPager.OnPageChangeListener{
private ViewPager mPager;
boolean mNeedsRedraw = false;
public PagerContainer(Context context)
{
super(context);
init();
}
public PagerContainer(Context context, AttributeSet attrs)
{
super(context, attrs);
init();
}
public PagerContainer(Context context, AttributeSet attrs, int defStyle)
{
super(context, attrs, defStyle);
init();
}
private void init()
{
// Disable clipping of children so non-selected pages are visible
setClipChildren(false);
// Child clipping doesn't work with hardware acceleration in Android
// 3.x/4.x
// You need to set this value here if using hardware acceleration in an
// application targeted at these releases.
setLayerType(View.LAYER_TYPE_SOFTWARE, null);
}
#Override
protected void onFinishInflate()
{
try
{
mPager = (ViewPager) getChildAt(0);
mPager.setOnPageChangeListener(this);
} catch (Exception e)
{
throw new IllegalStateException("The root child of PagerContainer must be a ViewPager");
}
}
public ViewPager getViewPager()
{
return mPager;
}
private Point mCenter = new Point();
private Point mInitialTouch = new Point();
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh)
{
mCenter.x = w / 2;
mCenter.y = h / 2;
}
#Override
public boolean onTouchEvent(MotionEvent ev)
{
// We capture any touches not already handled by the ViewPager
// to implement scrolling from a touch outside the pager bounds.
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
mInitialTouch.x = (int) ev.getX();
mInitialTouch.y = (int) ev.getY();
default:
ev.offsetLocation(mCenter.x - mInitialTouch.x, mCenter.y - mInitialTouch.y);
break;
}
return mPager.dispatchTouchEvent(ev);
}
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels)
{
// Force the container to redraw on scrolling.
// Without this the outer pages render initially and then stay static
if (mNeedsRedraw)
invalidate();
}
#Override
public void onPageSelected(int position)
{}
#Override
public void onPageScrollStateChanged(int state)
{
mNeedsRedraw = (state != ViewPager.SCROLL_STATE_IDLE);
}
`
And seting like this:
PagerContainer mContainer = (PagerContainer) mainView.findViewById(R.id.pagerContainer);
pager = mContainer.getViewPager();
pager.setOffscreenPageLimit(pagerAdapter.getCount());
pager.setPageMargin(getPx(20));
pager.setClipChildren(false);
But the side pages has the same size of the active page.
I've tried to use pager.setPageMargin but only sets margim between pages.
Can someone help me?
you need a PageTransfomer for that the changes the scale of the page on the left and on the right of the central one. When you implement the interface you'll be forced to implement the
public void transformPage (View page, float position) {
}
page is the current ViewPager's page, while float position is the position of page relative to the current center position of the pager. What you probably want is to change the scaleX and scaleY of page, when position is grater then 1 or less then -1. E.g.
public void transformPage (View page, float position) {
if (position > 1 || position < -1) {
ViewCompat.scaleX(page, 0.75f * paget.getWidth());
ViewCompat.scaleY(page, 0.75f * paget.getHeight());
} else if (position == 0) {
ViewCompat.scaleX(page, 1f);
ViewCompat.scaleY(page, 1f);
}
}
I have not the possibility to try it out so, please check for typo/cast.

make PageView view fast scrolling

i had a pageView, i use a code from dave to have some views on the same screen, like gallery widget, but the problem its seems its kind slow, not sure how to modify the ontouch event, what can i do to make fast scrollig i had this PageScroll code
public class PagerContainer extends FrameLayout implements ViewPager.OnPageChangeListener {
private ViewPager mPager;
boolean mNeedsRedraw = false;
public PagerContainer(Context context) {
super(context);
init();
}
public PagerContainer(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public PagerContainer(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
private void init() {
//Disable clipping of children so non-selected pages are visible
setClipChildren(false);
//Child clipping doesn't work with hardware acceleration in Android 3.x/4.x
//You need to set this value here if using hardware acceleration in an
// application targeted at these releases.
setLayerType(View.LAYER_TYPE_SOFTWARE, null);
}
#Override
protected void onFinishInflate() {
try {
mPager = (ViewPager) getChildAt(0);
mPager.setOnPageChangeListener(this);
} catch (Exception e) {
throw new IllegalStateException("The root child of PagerContainer must be a ViewPager");
}
}
public ViewPager getViewPager() {
return mPager;
}
private Point mCenter = new Point();
private Point mInitialTouch = new Point();
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
mCenter.x = w / 2;
mCenter.y = h / 2;
}
#Override
public boolean onTouchEvent(MotionEvent ev) {
//We capture any touches not already handled by the ViewPager
// to implement scrolling from a touch outside the pager bounds.
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
mInitialTouch.x = (int)ev.getX();
mInitialTouch.y = (int)ev.getY();
default:
ev.offsetLocation(mCenter.x - mInitialTouch.x, mCenter.y - mInitialTouch.y);
break;
}
return mPager.dispatchTouchEvent(ev);
}
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
//Force the container to redraw on scrolling.
//Without this the outer pages render initially and then stay static
if (mNeedsRedraw) invalidate();
}
#Override
public void onPageSelected(int position) { }
#Override
public void onPageScrollStateChanged(int state) {
mNeedsRedraw = (state != ViewPager.SCROLL_STATE_IDLE);
}
}
and this its the web where i take the code:
PageView code and this is the image of what i get since Gallery its depreacted this should be used, and gallery throws me outofmemory exception because im using images (i tried asyntask and a lot of things) Thanks

Categories

Resources