Custom View won't show with onDraw - android

I have built a custom view which extends LinearLayout. The view creates in its constructor several other views with a background of a shape I've created like this:
private void addBars()
{
setOrientation(LinearLayout.HORIZONTAL);
for(int i = 0; i < numberOfBars; i++)
{
if(bars[i] != null)
{
removeView(bars[i]);
}
View view = new View(getContext());
view.setBackgroundResource(R.drawable.rounded_corner_bars);
if(i < onBarIndex)
{
setBarColor(view, offBarColorBelowIndex);
}
else if(i == onBarIndex)
{
setBarColor(view, onBarColor);
}
else
{
setBarColor(view, offBarAfterIndex);
}
LayoutParams params = new LayoutParams((int) barWidth, LOWEST_BAR_HEIGHT + BAR_HEIGHT_DIFFERENCE*i);
params.setMargins(spaceBetweenBarsPixels, 0, spaceBetweenBarsPixels, 0);
params.gravity = Gravity.BOTTOM;
view.setLayoutParams(params);
bars[i] = view;
addView(view);
}
}
The onBarIndex can be changed from the outside and inside like this:
public void setOnBarIndex(int index)
{
onBarIndex = index;
addBars();
}
What I'm trying to do is update the index continuously and thus let the view redraw itself after a certain amount of interval. I'm doing it like this:
#Override
protected void onDraw(Canvas canvas)
{
super.onDraw(canvas);
if(recording && recorder != null)
{
int onIndex = (int)recorder.getOnIndex();
setOnBarIndex(onIndex);
postInvalidateDelayed(ANIMATION_INTERVAL);
}
}
The problem I'm facing is that when the onDraw method is present, the view is simply not drawing itself and the place in the layout where the view is supposed to be, seems blank. The onDraw method itself IS being called continuously and the recorder object can yield different results every time. If I comment the onDraw method then the view is showing like its supposed to, but then it won't redraw itself every interval.
Why is that?

Related

How to get View.invalidate working within Click Listener

I want to redraw a customview multiple times within a for loop in a ClickListener. However, the view is only refreshed once on completion of the ClickListener code, not within the for loop.
Here is the relevant code snippet
vw = (TouchEventView)findViewById(R.id.TouchEventView);
clear = (Button)findViewById(R.id.button2);
clear.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
coords = TouchEventView.getFloats(TouchEventView.points);
if (coords.length < 4) {
Toast.makeText(MainActivity.this,"You must have at least one line",Toast.LENGTH_LONG).show();
}
else if ( coords.length < 6) {
SendData(length(coords[0],coords[1],coords[2],coords[3]));
SendData("s");
}
else {
for (int i = 0; i<coords.length-4; i=i+4) {
SendData(length(coords[i],coords[i+1],coords[i+2],coords[i+3]));
SendData(turn(coords[i],coords[i+1],coords[i+2],coords[i+3],coords[i+6],coords[i+7]));
}
int i = coords.length-4;
SendData(length(coords[i],coords[i+1],coords[i+2],coords[i+3]));
SendData("s");
// Now do the overlay
TouchEventView.retrace = true;
reords = new float[coords.length];
for (int j=0; j<coords.length-1; j=j+4) {
try {
Thread.sleep(1000);
}
catch (Exception e) {
}
// Log.e("pitrack","i : " + j);
TouchEventView.leg = j;
vw.postInvalidate();
}
}
}
});
and here is the onDraw method of the customview (TouchEventView)
#Override
protected void onDraw(Canvas canvas) {
if (retrace) {
canvas.drawLines(MainActivity.coords,paintRed);
canvas.drawLine(MainActivity.coords[leg],MainActivity.coords[leg+1],MainActivity.coords[leg+2],MainActivity.coords[leg+3],paintGreen);
}
else {
if (points.size() > 3) {
canvas.drawLines(getFloats(points),paintRed);
}
if (x > -1) {
canvas.drawLine(x, y, x1, y1, paintRed);
canvas.drawBitmap(finish,x1-96,y1-96,paintRed);
canvas.drawBitmap(start,startx-48,starty-48,paintRed);
}
}
}
Neither vw.invalidate() nor vw.postInvalidate() work other than at the end of the ClickListener process.
How can I force a redraw of the screen within the for loop?
The only solution I have found so far is to embed the customview.postInvalidate() as a runnable in a separate thread.
It is a shady workaround BUT you can force the view to invalidate, if you add another onclickListener off somekind. It worked for me when i came to showing a Progressbar while uploading something in the main thread (i had to do it that way). It is not pretty but it is working. Good Luck with that.

How to use SurfaceView to overlay another SurfaceView and why canvas be null?

I'm trying to use SurfaceView with Canvas to draw a waveform. I'm using one SurfaceView with Canvas to draw, and it works.
But when I want to make my first SurfaceView overlay by the second SurfaceView(using FrameLayout). It's doesn't work.
And these two questions appears to me:
1. If I use Canvas in second SurfaceView, then the second canvas becomes null;
2. If I don't use Canvas in second SurfaceView, but just call overlay, then the SurfaceView size will be overlay but the graph is same.
Referenced code is as below:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.e("onCreate", "here");
l1 = (LinearLayout) findViewById(R.id.l1);
l2 = (LinearLayout) findViewById(R.id.l2);
l2.setVisibility(View.INVISIBLE);
sfv = (SurfaceView) findViewById(R.id.sfv);
sfh = sfv.getHolder();
sfv.getHolder().setFormat(PixelFormat.TRANSLUCENT);
sfv2 = (SurfaceView) findViewById(R.id.sfv2);
sfh2 = sfv2.getHolder();
sfv2.getHolder().setFormat(PixelFormat.TRANSLUCENT);
paint.setColor(Color.BLUE);
paint.setStrokeWidth(3);
paint2.setColor(Color.RED);
paint2.setStrokeWidth(3);
Log.i("flow", "now at before init()");
tv = (TextView) findViewById(R.id.tv);
sfh.addCallback(this);
sfh2.addCallback(this);
//init();
}
public void init() {
Log.e("init", "here");
if (pic) {
canvas = sfh.lockCanvas(new Rect(xtime, 0, xtime + 2, getWindowManager().getDefaultDisplay().getHeight()));
canvas.drawARGB(255, 0, 0, 0);
for (int i = 0; i < 600; i++) {
a = 600 - i;
canvas.drawLine(xtime, oldy, xtime + 2, a, paint);
xtime += 2;
oldy = a;
if (xtime > 1000) {
xtime = 0;
oldy = 0;
}
}
sfh.unlockCanvasAndPost(canvas);
// tv.setText("in the init2");
}
else{
Log.e("sfh2", "here");
canvas = sfh2.lockCanvas(new Rect(xtime2, 0, xtime2 + 2, getWindowManager().getDefaultDisplay().getHeight()));
if(canvas == null){
Log.e("canvas", "null here");
}
else {
canvas.drawARGB(255, 255, 255, 255);
for (int i = 0; i < 300; i++) {
a = 300 - i;
canvas.drawLine(xtime2, oldy, xtime2 + 2, a, paint2);
xtime2 += 2;
oldy = a;
if (xtime2 > 1000) {
xtime2 = 0;
oldy2 = 0;
}
}
sfh2.unlockCanvasAndPost(canvas);
}
}
}
#Override
public void surfaceCreated(SurfaceHolder holder) {
Log.e("surfaceCreated", "here");
init();
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
Log.e("surfaceChanged", "here");
init();
}
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
Log.e("surfaceDestroyed", "here");
}
public void onchange(View view) {
if (pic) {
pic = false;
l1.setVisibility(View.VISIBLE);
l2.setVisibility(View.INVISIBLE);
tv.setText("change sf2");
Log.e("pic", "pic " + pic);
} else {
pic = true;
l1.setVisibility(View.INVISIBLE);
l2.setVisibility(View.VISIBLE);
tv.setText("change sf1");
Log.e("pic", "pic " + pic);
}
}
I have been in confusion for two weeks, hope somebody can help me to resolve this issue.
Thanks everyone.
First and foremost, bear in mind that a SurfaceView has two parts, the Surface and the View. The View part acts like any other View. The Surface is created asynchronously, and is a completely separate layer.
Your current code is creating two overlapping Surfaces with the same Z-ordering, which means they're trying to occupy the same space at the same time. The results are undefined, but generally speaking you will see one Surface but not the other. Use e.g. setZOrderMediaOverlay() on the one that should be in front of the other. See Grafika's "multi-surface test" Activity for an example with three overlapping Surfaces.
The Surfaces are created asynchronously, and not at the same time, so you should expect your surfaceCreated() callback to fire twice. It looks like you're calling init() each time. If it first fires for Surface A, and init() tries to draw on Surface B, the method will fail, because it's trying to draw on a Surface that hasn't been created yet. Check the value of SurfaceHolder in surfaceCreated(), and and only draw on the Surface that was just created. (Alternatively, wait until they've both been created, and then draw on both.)

Android: custom views get drawn at wrong x,y coordinates

I'm trying to create a custom view, inherit from view group, and layout custom sub-views inside this view group in a customized way. Basically I'm trying to create a calendar view similar to the one in outlook, where each event takes up screen height relative to its length.
I initialize an ArrayList of View in the ViewGroup's constructor, override onMeasure, onLayout and onDraw, and everything works well, except... the rendered views all render starting at (0,0), even though I set their left and right properties to other values. Their width and height come out ok, only their top and left are wrong.
This is the code, which I abbreviated for clarity and simplicity:
public class CalendarDayViewGroup extends ViewGroup {
private Context mContext;
private int mScreenWidth = 0;
private ArrayList<Event> mEvents;
private ArrayList<View> mEventViews;
// CalendarGridPainter is a class that draws the background grid.
// this one works fine so I didn't write its actual code here.
// it just takes a Canvas and draws lines on it.
// I also tried commenting out this class and got the same result,
// so this is DEFINITELY not the problem.
private CalendarGridPainter mCalendarGridPainter;
public CalendarDayViewGroup(Context context, Date date) {
super(context);
init(date, context);
}
//... other viewGroup constructors go here...
private void init(Date date, Context context) {
mContext = context;
// the following line loads events from a database
mEvents = AppointmentsRepository.getByDateRange(date, date);
// inflate all event views
mEventViews = new ArrayList<>();
LayoutInflater inflater = LayoutInflater.from(mContext);
for (int i = 0; i < mEvents.size(); i++) {
View view = getSingleEventView(mEvents.get(i), inflater);
mEventViews.add(view);
}
// set this flag so that the onDraw event is called
this.setWillNotDraw(false);
}
private View getSingleEventView(Event event, LayoutInflater inflater) {
View view = inflater.inflate(R.layout.single_event_view, null);
// [set some properties in the view's sub-views]
return view;
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.getSize(heightMeasureSpec));
// get screen width and create a new GridPainter if needed
int screenWidth = MeasureSpec.getSize(widthMeasureSpec);
if (mScreenWidth != screenWidth)
{
mScreenWidth = screenWidth;
mCalendarGridPainter = new CalendarGridPainter(screenWidth);
}
int numChildren = mEvents.size();
for (int i = 0; i < numChildren; i++) {
View child = mEventViews.get(i);
Event event = mEvents.get(i);
// event width is the same as screen width
int specWidth = MeasureSpec.makeMeasureSpec(mScreenWidth, MeasureSpec.EXACTLY);
// event height is calculated by its length, the calculation was ommited here for simplicity
int eventHeight = 350; // actual calculation goes here...
int specHeight = MeasureSpec.makeMeasureSpec(eventHeight, MeasureSpec.EXACTLY);
child.measure(specWidth, specHeight);
}
}
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int numChildren = mEvents.size();
for (int i = 0; i < numChildren; i++) {
View child = mEventViews.get(i);
Event event = mEvents.get(i);
int eventLeft = 0;
int eventTop = (i + 1) * 200; // test code, make each event start 200 pixels after the previous one
int eventWidth = eventLeft + child.getMeasuredWidth();
int eventHeight = eventTop + child.getMeasuredHeight();
child.layout(eventLeft, eventTop, eventWidth, eventHeight);
}
}
#Override
protected void onDraw(Canvas canvas) {
// draw background grid
mCalendarGridPainter.paint(canvas);
// draw events
for (View view : mEventViews) {
view.draw(canvas);
}
super.onDraw(canvas);
}
}
For some reason, it seems like the way children are drawn with ViewGroups is that the ViewGroup translates the canvas to child's position then draws the child at 0,0.
But as it turns out, ViewGroup will handle all the drawing of children for you. I think if you simplify your onDraw() method you should be all set:
#Override
protected void onDraw(Canvas canvas) {
// draw background grid
mCalendarGridPainter.paint(canvas);
// draw events
super.onDraw(canvas);
}
Now that I'm looking at your code further, I noticed you are inflating your child views within the code for your ViewGroup. It would be best to do all that outside your ViewGroup, add those views using addView(), then use getChildCount() and getChildAt() to access the child views during onLayout().

ScrollView with multiple LinearLayouts doesn't size correctly

I have created a GridView control, which inhertis from a ScrollView, the idea of this control, is that it will contain multiple Views arranged in a grid format with a given number of columns and rows.
When the view is first built, the GridView doesn't know the size of its container, so I wait until the onSizeChanged method is called, then I apply the relevant sizing.
When the below is run, it doesn't re-size the grid to show it correctly, each control is only as big as it needs to be to show the text.
When the `onSizeChanged' method is called, it has the correct size, and applies the correct size to each child view, but it doesn't affect the way the controls are drawn (i.e. they're still all bunched up on the top left of the screen).
Despite this, I have actually got it working, but it draws twice. I do this by creating a Runnable which calls ResizeList. Then calling new Handler().post(r) straight after I call BuildIt.
Although this is a solution, I just don't understand why it doesn't work in the below form.
Incidentally, if the GridView is the main View added to the Activity, it displays fine, it's only when it's subsequently added. Which is why I have the Button, which you have to press to show the grid.
Can anyone suggest why the below code doesn't work properly?
public class MainActivity extends Activity {
GridView sv;
FrameLayout flay;
#Override protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
flay=new FrameLayout(this);
this.setContentView(flay);
Button b=new Button(this);
b.setText("press me to show grid view");
b.setOnClickListener(ocl);
flay.addView(b);
}
OnClickListener ocl=new OnClickListener()
{
#Override public void onClick(View v)
{
BuildIt();
}};
private void BuildIt()
{
flay.removeAllViews(); // remove the button control
sv=new GridView(this);
for (int c1=0 ; c1<30 ; c1++)
{
TextView tv=new TextView(this);
tv.setText("Item "+c1);
tv.setGravity(android.view.Gravity.CENTER);
sv.addListItem(tv);
}
flay.addView(sv);
sv.ConstructList();
}
}
The GridView class
public class GridView extends ScrollView
{
final int rows=4;
final int cols=4;
private ArrayList<View> allViews=new ArrayList<View>();
private LinearLayout ll;
public GridView(Context context)
{
super(context);
ll=new LinearLayout(context);
ll.setOrientation(LinearLayout.VERTICAL);
this.addView(ll);
}
public void addListItem(View v)
{
allViews.add(v);
}
public void ConstructList()
{
int c1=0;
ll.removeAllViews(); // Just in case we're re-building
LinearLayout row=null;
for (View v : allViews)
{
if (c1%cols==0)
{
row=new LinearLayout(this.getContext());
ll.addView(row);
}
row.addView(v);
c1++;
}
}
private void ResizeList()
{
int useHeight=getHeight()/rows;
int useWidth=getWidth()/cols;
LinearLayout.LayoutParams lpCol=new LinearLayout.LayoutParams(useWidth, useHeight);
Log.i("log","About to set width/height="+useWidth+"/"+useHeight);
int numKids= ll.getChildCount();
for (int c1=0 ; c1<numKids ; c1++)
{
LinearLayout ll2=(LinearLayout)ll.getChildAt(c1);
for (int c2=0 ; c2<ll2.getChildCount() ; c2++) // use getChildCount rather than cols, just in case it's the last one
{
View v=ll2.getChildAt(c2);
v.setLayoutParams(lpCol);
}
}
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh)
{
super.onSizeChanged(w, h, oldw, oldh);
ResizeList();
}
}
I have a function which is used to resize the child's width and height in gridView.
May be this could help you :
public static void setGridChild_Height(GridView gridView, int columns) {
ListAdapter listAdapter = gridView.getAdapter();
if (listAdapter == null) {
// pre-condition
return;
}
int totalHeight = 0;
int items = listAdapter.getCount();
int rows = 0;
for (int j = 0; j < items; j++) {
View view = gridView.getChildAt(j);
if (view != null && view.getHeight() > totalHeight) {
totalHeight = view.getHeight();
}
}
System.out.println("totalHeight -> " + totalHeight);
if (totalHeight > 0) {
for (int j = 0; j < items; j++) {
View view = gridView.getChildAt(j);
if (view != null && view.getHeight() < totalHeight) {
view.setMinimumHeight(totalHeight);
}
}
}
// View listItem = listAdapter.getView(0, null, gridView);
// listItem.measure(0, 0);
// totalHeight = listItem.getMeasuredHeight();
//
// float x = 1;
// if (items > columns) {
// x = items / columns;
// rows = (int) (x + 1);
// totalHeight *= rows;
// }
//
// ViewGroup.LayoutParams params = gridView.getLayoutParams();
// params.height = totalHeight;
// gridView.setLayoutParams(params);
}
Try to change logic as per your requirement.
Code is not tested perfectly.
It's because onSizeChanged when newly added to the view hierarchy uses it's old sizes of "0" (according to the docs: http://developer.android.com/reference/android/view/View.html#onSizeChanged(int, int, int, int))
I think what you want is to a addOnLayoutChangedListener : http://developer.android.com/reference/android/view/View.html#addOnLayoutChangeListener(android.view.View.OnLayoutChangeListener)
Using the ViewTreeObserver might be another option that will work for you: How can you tell when a layout has been drawn?

Add footer to Android TouchListView

Hi I'm using the TouchListView control from here: https://github.com/commonsguy/cwac-touchlist
and I've added some buttons to add to the list in the footer:
mFooter = getLayoutInflater().inflate(R.layout.edit_homepage_footer_layout, null);
mListView = (TouchListView) findViewById(R.id.sectionList);
mListView.addFooterView(mFooter);
It all seems to be working fine until I drag an item in the list, at which point the footer collapses (to the height of one list item I think) obscuring the buttons I have added.
Can anyone suggest a fix/workaround for this?
I actually worked this out shortly after asking it (always the way...)
The issue is in the doExpansion() and unExpandViews() methods which were modifying every item in the list including the footer. To fix it I created a method to check whether we are dealing with a draggable item or the footer:
private boolean isDraggableItem(View view) {
View dragger = view.findViewById(grabberId);
return dragger != null;
}
And then modified the methods mentioned as follows:
private void unExpandViews(boolean deletion) {
for (int i = 0; ; i++) {
View v = getChildAt(i);
if (v == null) {
if (deletion) {
// HACK force update of mItemCount
int position = getFirstVisiblePosition();
int y = getChildAt(0).getTop();
setAdapter(getAdapter());
setSelectionFromTop(position, y);
// end hack
}
layoutChildren(); // force children to be recreated where needed
v = getChildAt(i);
if (v == null) {
break;
}
}
if (isDraggableItem(v)) { //check this view isn't the footer
ViewGroup.LayoutParams params = v.getLayoutParams();
params.height = mItemHeightNormal;
v.setLayoutParams(params);
v.setVisibility(View.VISIBLE);
}
}
}
private void doExpansion() {
Log.d(logTag, "Doing expansion");
int childnum = mDragPos - getFirstVisiblePosition();
if (mDragPos > mFirstDragPos) {
childnum++;
}
View first = getChildAt(mFirstDragPos - getFirstVisiblePosition());
for (int i = 0; ; i++) {
View vv = getChildAt(i);
if (vv == null) {
break;
}
int height = mItemHeightNormal;
int visibility = View.VISIBLE;
if (vv.equals(first)) {
// processing the item that is being dragged
if (mDragPos == mFirstDragPos) {
// hovering over the original location
visibility = View.INVISIBLE;
} else {
// not hovering over it
height = 1;
}
} else if (i == childnum) {
if (mDragPos < getCount() - 1) {
height = mItemHeightExpanded;
}
}
if (isDraggableItem(vv)) { //check this view isn't the footer
ViewGroup.LayoutParams params = vv.getLayoutParams();
params.height = height;
vv.setLayoutParams(params);
vv.setVisibility(visibility);
}
}
}
Would be worth updating the github project to include this I think.

Categories

Resources