I want to create a custom view that animates some lines. so I created a class that extends View class. I called onDraw() & drawn on the canvas.
Here are some codes that I've approached so far.
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
p += 10;
canvas.drawLine(5, 5, p, 5, mPaint);
invalidate();
}
note that p & mPaint are instantiated at the constructor.
but, even I called invalidate() method, it doesn't update the canvas, i.e. doesn't animate the line.
so, how to solve this?
Are you sure it's not drawing? What colour is your Paint object?
I created a blank 4.4 android application, added this class:
package ca.kieve.playground;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.View;
public class AnimView extends View {
private int p = 0;
private Paint mPaint;
public AnimView(Context context) {
super(context);
init();
}
public AnimView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
private void init() {
mPaint = new Paint();
mPaint.setColor(Color.RED);
mPaint.setStrokeWidth(10);
}
#Override
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
p += 1;
canvas.drawLine(5, 5, p, 5, mPaint);
invalidate();
}
}
and changed the activity_main.xml to this:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="#dimen/activity_vertical_margin"
android:paddingLeft="#dimen/activity_horizontal_margin"
android:paddingRight="#dimen/activity_horizontal_margin"
android:paddingTop="#dimen/activity_vertical_margin"
tools:context="ca.kieve.playground.MainActivity" >
<ca.kieve.playground.AnimView
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</RelativeLayout>
This draws with no issues.
remove
invalidate() form onDraw().
use
inavalidate() while you have done with your changes.
Related
I have a problem , I hope you will bring me some informations.
In order to have a circular VideoView , I put it in a CardView
<android.support.v7.widget.CardView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/cardVideo"
app:cardCornerRadius="180dp"
android:background="#000">
<com.twilio.video.VideoView
android:id="#+id/videoView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="visible" />
</android.support.v7.widget.CardView>
But the problem is that I'm building my application on multiple tablet and the cardCornerRadius isn't adapted to screen size , 180dp is too big for a 8 inch tablet so my VideoView appears in DIAMONDS see :
and for example in a 10 inch tablet it's a perfect circle :
I tried to get device inches programmatically and use setRadius() depend on it but it's not perfect and I don't think that it's the correct way.
What can I do to find the good corner radius adapted to tablet ? Thanks
Ok, I found your answer:
Add this class in your project
package com.example.myapplication;
import android.content.Context;
import android.graphics.*;
import android.util.AttributeSet;
import android.widget.FrameLayout;
public class RoundedCornerLayout extends FrameLayout {
private Path path = new Path();
public RoundedCornerLayout(Context context) {
super(context);
}
public RoundedCornerLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public RoundedCornerLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
// compute the path
float halfWidth = w / 2f;
float halfHeight = h / 2f;
float centerX = halfWidth;
float centerY = halfHeight;
path.reset();
path.addCircle(centerX, centerY, Math.min(halfWidth, halfHeight), Path.Direction.CW);
path.close();
}
#Override
protected void dispatchDraw(Canvas canvas) {
int save = canvas.save();
canvas.clipPath(path);
super.dispatchDraw(canvas);
canvas.restoreToCount(save);
}
}
and put your VideoView inside it. like here :
<com.example.myapplication.RoundedCornerLayout
android:layout_width="100dp"
android:layout_height="100dp">
// place your VideoView
<ImageView
android:layout_width="match_parent"
android:src="#color/colorPrimary"
android:layout_height="match_parent"/>
</com.example.myapplication.RoundedCornerLayout>
references : 1 2
I have circular picture of timer:
I want to create circular progress bar which would consist of this picture, which would make it slowly empty like a clock until full disappearance (like an animation).
for example, after half of the time I will define it's appear as:
Right now I'm doing it in this way:
I put the timer image as image on the screen, and over it I put regular circle progress bar with color as the background color which hides the image (and so I get the desired effect).
but now I have problem when I have few colors in the background of the app, because I can't to make the progress bar with the same color as the background, so I looking for new way to make it, like custom progress bar that consist from the timer image with transparent background.
please help me how to change my code to get it:
ProgressBar XML:
<ProgressBar
android:id="#+id/progressBar"
android:layout_width="200dp"
android:layout_height="200dp"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:progress="100"
android:rotation="90"
style="?android:progressBarStyleHorizontal"
android:progressDrawable="#drawable/prog_blue"/>
Prog_blue (the drawable of the progressBar) - blue is the background color of the app:
<?xml version="1.0" encoding="utf-8"?>
<scale
android:fromDegrees="270"
android:pivotX="50%"
android:pivotY="50%"
xmlns:android="http://schemas.android.com/apk/res/android">
<shape
android:shape="ring"
android:innerRadiusRatio="2.7"
android:thickness="23dp"
xmlns:android="http://schemas.android.com/apk/res/android">
<gradient
android:startColor="#color/prog_blue"
android:endColor="#color/prog_blue"
android:type="sweep"
android:useLevel="false"/>
</shape>
</scale>
you can create custom View To achieve that
here My Code :
ClockProgressBar.java
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.View;
public class ClockProgressBar extends View {
Paint tickPaint;
private int shortTickLen=10;
private int longTickLen=20;
private int finalWidth=0;
private int finalHeight=0;
private int r=0;
private Path ticksPath;
int tickNumbers=60;
private ValueAnimator animator;
public ClockProgressBar(Context context) {
super(context);
ini();
}
public ClockProgressBar(Context context, #Nullable AttributeSet attrs) {
super(context, attrs);
ini();
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
drawIntervals(canvas);
}
void drawIntervals(Canvas canvas){
double cX = getX()+finalWidth/2;
double cY = getY()+finalHeight/2;
ticksPath=new Path();
for ( int i=1; i<= tickNumbers; i++){
int len = shortTickLen;
if ( i % 15 == 0 ){
len = longTickLen;
}else if ( i % 5 == 0 ){
len = longTickLen;
}
double di = (double)i;
double angleFrom12 = di/60.0*2.0*Math.PI;
double angleFrom3 = Math.PI/2.0-angleFrom12;
ticksPath.moveTo(
(float)(cX+Math.cos(angleFrom3)*r),
(float)(cY-Math.sin(angleFrom3)*r)
);
ticksPath.lineTo(
(float)(cX+Math.cos(angleFrom3)*(r-len)),
(float)(cY-Math.sin(angleFrom3)*(r-len))
);
}
canvas.drawPath(ticksPath,tickPaint);
}
void ini(){
tickPaint=new Paint();
tickPaint.setStrokeCap(Paint.Cap.BUTT);
tickPaint.setStrokeJoin(Paint.Join.ROUND);
tickPaint.setAntiAlias(true);
tickPaint.setStyle(Paint.Style.STROKE);
tickPaint.setStrokeWidth(8);
tickPaint.setStrokeMiter(2f);
tickPaint.setColor(Color.BLACK);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
finalWidth = getMeasuredWidth() / 2;
finalHeight = getMeasuredWidth() / 2;
r = (finalWidth / 2);
setMeasuredDimension(finalWidth, finalHeight );
}
public void startAnimation(){
animator = ValueAnimator.ofInt(60, 0);
animator.setDuration(5000);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
public void onAnimationUpdate(ValueAnimator animation) {
tickNumbers= (int) animation.getAnimatedValue();
invalidate();
}
});
animator.setRepeatCount(Integer.MAX_VALUE);
animator.start();
}
public void stopAnimation(){
if (animator!=null){
animator.cancel();
}
setVisibility(GONE);
}
}
and use it in xml
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<<Your Package Name>.ClockProgressBar
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/progress"/>
</LinearLayout>
and call it in you activity and use startAnimation() and stopAnimation() to show and dismiss the View
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ClockProgressBar progressBar=findViewById(R.id.progress);
progressBar.startAnimation();
}
}
final result:
hope it helps
Im try to create a drawing app. In my main activity I have an XML file which contains a drawing viewview
<app.color.DrawingView
android:layout_width="match_parent"
android:layout_height="200dp"
android:id="#+id/drawing"
android:layout_marginLeft="5dp"
android:layout_marginRight="5dp"
android:background="#FFFFFFFF"
android:layout_above="#+id/settings"
android:layout_alignParentTop="true" />
This is based on my drawing view class:
package app.color;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
public class DrawingView extends View {
private Paint paint = new Paint();
private Path path = new Path();
public DrawingView(Context context, AttributeSet attrs) {
super(context, attrs);
paint.setAntiAlias(true);
paint.setStrokeWidth(5f);
paint.setColor(Color.BLACK);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeJoin(Paint.Join.ROUND);
}
#Override
protected void onDraw(Canvas canvas) {
canvas.drawPath(path, paint);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
// Get the coordinates of the touch event.
float eventX = event.getX();
float eventY = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// Set a new starting point
path.moveTo(eventX, return true;
case MotionEvent.ACTION_MOVE:
// Connect the points
path.lineTo(eventX, eventY);
break;
default:
return false;
}
// Makes our view repaint and call onDraw
invalidate();
return true;
}
public void setColor(String newColor){
invalidate();
int paintColor = Color.parseColor(newColor);
paint.setColor(paintColor);
}
}
Then in another activity on a button click I have a method which which I want to be able to call the setColor method in drawingView to change the color. However it wont let me do this because a "non-static method cannot be referenced from a static context"
public void toBlack(View view)
{
DrawingView.setColor("#00000");
Intent intent = new Intent(Colors.this, MainActivity.class);
startActivity(intent);
}
Create a instance of DrawingView.
thats like ,
DrawingView drawingview = new DrawingView();
drawingview.setColor("#00000");
setColor is an instance method, meaning you need an instance of the DrawingView class in order to call it. You're attempting to call it on the DrawingView type itself.
just make your code to
DrawingView dv = new DrawingView();
dv.setColor("#00000");
Intent intent = new Intent(Colors.this, MainActivity.class);
startActivity(intent);
for example you already have
public DrawingView(Context context, AttributeSet attrs) {
super(context, attrs);
paint.setAntiAlias(true);
paint.setStrokeWidth(5f);
paint.setColor(Color.BLACK);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeJoin(Paint.Join.ROUND);
}
you can also have another empty constructor
like
public DrawingView() { }
setColor is not a static method. You need to call it on an instance of DrawingView.
Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 8 years ago.
Improve this question
I have been trying to add a erase function to this drawing app. I was planning on it just simpily drawing white over where you move your finger. For some reason i cannot get the button to work. If i can get this button to work I will be able to add more colors ect.
(i have not done anything with Mainactivity)
package com.example.draw;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.os.Bundle;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
public class MainDrawingView extends View
{
private Paint paint = new Paint();
private boolean erase;
private Path path = new Path();
Button aButton;
public MainDrawingView(Context context, AttributeSet attrs)
{
super(context, attrs);
paint.setAntiAlias(true);
paint.setStrokeWidth(5f);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeJoin(Paint.Join.ROUND);
aButton = (Button) this.findViewById(R.id.button1);
aButton.setOnClickListener(new OnClickListener()
{
public void onClick(View v)
{
erase = !erase;
}
});
}
#Override
protected void onDraw(Canvas canvas)
{
canvas.drawPath(path, paint);
}
public boolean onTouchEvent(MotionEvent event)
{
if(erase) paint.setColor(Color.WHITE);
else paint.setColor(Color.BLACK);
// Get the coordinates of the touch event.
float eventX = event.getX();
float eventY = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// Set a new starting point
path.moveTo(eventX, eventY);
return true;
case MotionEvent.ACTION_MOVE:
// Connect the points
path.lineTo(eventX, eventY);
break;
default:
return false;
}
// Makes our view repaint and call onDraw
invalidate();
return true;
}
}
Activity_main<
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#ffffff"
tools:context="com.example.draw.FullscreenActivity">
<!-- This is the view on which we will draw. -->
<view
android:layout_width="match_parent"
android:layout_height="match_parent"
class="com.example.draw.MainDrawingView"
android:id="#+id/single_touch_view"
android:layout_gravity="left|top"
android:background="#ffffff" />
<Button
android:id="#+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Button" />
</FrameLayout>
It seems like your problem is that you're using the wrong types. Your erase variable is of type Paint:
private Paint erase = new Paint();
But in your code you're treating it like an int that represents a boolean.
if(erase != 0) { paint.setColor(Color.BLACK); }
if(erase == 0) { paint.setColor(Color.WHITE); }
...
erase = 1;
Secondly, you're never actually setting the onClickListener for your button. Also, the logic to set the color should all be moved into either your onTouch or the onClick method. You really should have:
public class MainDrawingView extends View
{
private Paint paint = new Paint();
private boolean erase;
private Path path = new Path();
Button aButton;
public MainDrawingView(Context context, AttributeSet attrs)
{
super(context, attrs);
paint.setAntiAlias(true);
paint.setStrokeWidth(5f);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeJoin(Paint.Join.ROUND);
aButton = (Button) this.findViewById(R.id.button1);
aButton.setOnClickListener(new OnClickListener()
{
public void onClick(View v)
{
erase = !erase;
}
});
}
#Override
protected void onDraw(Canvas canvas)
{
canvas.drawPath(path, paint);
}
public boolean onTouchEvent(MotionEvent event)
{
if(erase) paint.setColor(Color.WHITE);
else paint.setColor(Color.BLACK);
// Get the coordinates of the touch event.
float eventX = event.getX();
float eventY = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// Set a new starting point
path.moveTo(eventX, eventY);
return true;
case MotionEvent.ACTION_MOVE:
// Connect the points
path.lineTo(eventX, eventY);
break;
default:
return false;
}
// Makes our view repaint and call onDraw
invalidate();
return true;
}
}
It is possible that your view is not being created with the following constructor
Context context, AttributeSet attrs
It might be using a different default constructor that you have not overridden. Then nothing would get setup and you would not get any click behavior that your expecting from your listener.
It looks like you do not have your onCreate(Bundle savedInstances) method setup to to add the listener correctly.
I am testing drawing audio frequency into canvas using canvas.drawLine() method. I am able to do static draw into canvas. Basically I have a test app which has two buttons START and STOP and a Canvas where i am trying to draw the audio frequencies obtained from FFT. When i hit the START button it starts recording sound using AudioRecord class and collects data into buffer which i run through FFT to get the frequencies. This is in very early stage. Right now i am only trying to figure out how to pass these frequency values to onDraw method of my Custom View class. Here are the relevent codes starting from the layout.
activity_main.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="#dimen/activity_vertical_margin"
android:paddingLeft="#dimen/activity_horizontal_margin"
android:paddingRight="#dimen/activity_horizontal_margin"
android:paddingTop="#dimen/activity_vertical_margin"
tools:context=".MainActivity" >
<com.example.testapp.WaveForm
android:id="#+id/waveForm1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentRight="true"
android:layout_alignParentTop="true"
android:layout_marginTop="78dp" />
<Button
android:id="#+id/button2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBaseline="#+id/button1"
android:layout_alignBottom="#+id/button1"
android:layout_alignRight="#+id/waveForm1"
android:layout_marginRight="19dp"
android:text="STOP" />
<Button
android:id="#+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignLeft="#+id/waveForm1"
android:layout_alignParentTop="true"
android:text="START" />
</RelativeLayout>
I think its very starighforward. Here is my Custom View class
WaveForm.java
package com.example.testapp;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.View;
public class WaveForm extends View {
private Paint mLinePaint;
float x ;
float y;
public WaveForm(Context context, AttributeSet attrs) {
super(context, attrs);
mLinePaint = new Paint();
mLinePaint.setStyle(Paint.Style.STROKE);
mLinePaint.setARGB(255, 0, 0, 255);
mLinePaint.setAntiAlias(true);
mLinePaint.setStrokeWidth(10);
x = 0.0f;
y = 0.0f;
}
#Override
protected void onDraw(Canvas canvas) {
//Instead of 50 here i would like to pass the value obtained from the Activity class
canvas.drawLine(x, 0, y, 50, mLinePaint);
y = y + 1.0f;
x = x + 1.0f;
super.onDraw(canvas);
}
}
As i mentioned in the comment in the onDraw method, i would like to pass the frequency value i obtained in the Activity instead of 50.
Finally here is my Activity:
MainActivity.java
package com.example.testapp;
import android.app.Activity;
import android.graphics.Canvas;
import android.media.AudioFormat;
import android.media.AudioRecord;
import android.media.MediaRecorder.AudioSource;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
public class MainActivity extends Activity {
boolean recording = false;
AudioRecord audioRecord;
double[] x;
double[] y;
double currentPoint = 0;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final View myInstance = (View) findViewById(R.id.waveForm1);
Button start = (Button) findViewById(R.id.button1);
start.setOnClickListener(new OnClickListener(){
#Override
public void onClick(View v) {
int sampleRateInHz=44100;
int channelConfig = AudioFormat.CHANNEL_IN_MONO;
int bufferSizeInBytes = AudioRecord.getMinBufferSize(sampleRateInHz, channelConfig, AudioFormat.ENCODING_PCM_16BIT);
audioRecord = new AudioRecord(AudioSource.MIC, sampleRateInHz, channelConfig, AudioFormat.ENCODING_PCM_16BIT, AudioRecord.getMinBufferSize(sampleRateInHz, channelConfig, AudioFormat.ENCODING_PCM_16BIT));
byte[] audioData = new byte[bufferSizeInBytes/4];
FFT myFFT = new FFT(audioData.length);
Canvas canvas = new Canvas();
recording = true;
audioRecord.startRecording();
while(recording) {
audioRecord.read(audioData,0, bufferSizeInBytes/4);
//convert to double here
for(int i=0; i<audioData.length; i++) {
x[i] = (double)audioData[i];
y[i] = 0;
}
double[] samples = myFFT.fft(x, y);
for(int i=0; i<samples.length; i++) {
currentPoint = samples[i];
myInstance.draw(canvas);
myInstance.invalidate();
}
}
}
});
Button stop = (Button) findViewById(R.id.button2);
stop.setOnClickListener(new OnClickListener(){
#Override
public void onClick(View v) {
recording = false;
audioRecord.stop();
}
});
}
}
The currentPoint variable is what i am trying to pass to the onDraw method. Thanks in advance for taking a look.
Create Getter/Setter for currentPoint variable inside WaveForm to get value from MainActivity before calling draw as:
public class WaveForm extends View {
private double currentPoint = 50;
public void setCurrentPoint(double currentPoint){
this.currentPoint=currentPoint;
}
public double getCurrentPoint(){
return this.currentPoint;
}
#Override
protected void onDraw(Canvas canvas) {
canvas.drawLine(x, 0, y, getCurrentPoint(), mLinePaint);
...
}
}
and from MainActivity set currentPoint value by calling setCurrentPoint method before calling onDraw:
for(int i=0; i<samples.length; i++) {
currentPoint = samples[i];
myInstance.setCurrentPoint(currentPoint);
myInstance.draw(canvas);
myInstance.invalidate();
}
Specify currentPoint as a static variable, and use MainActivity.currentPoint to get the value of the variable from your other class. Alternatively, just use a getter method that returns the variable.