Pour développer des applications Android, il faut au moins disposer du Android SDK et du kit de développement Java. Pour faciliter le développement, il est conseillé d’untiliser un EDI/IDE (Integrated Development Environment) comme Android Studio.
Android Studio est un environnement de développement pour développer des applications Android :
Android Studio permet principalement d’éditer les fichiers Java et les fichiers de configuration d’une application Android.
Il propose entre autres des outils pour gérer le développement d’applications multilingues et permet de visualiser la mise en page des écrans sur des écrans de résolutions variées simultanément.
Quelques raccourcis intéressants pour le développeur :
Démarrer Android Studio :
$ studio.sh
Choisir ‘Empty Activity’ :
Cliquer sur ‘Run app’ (pour tester).
Pour la cible, vous avez le choix entre le smartphone (ou la tablette) connecté ou un émulateur (cliquer sur ‘Create New Emulateur’ pour en créer un) :
Par défaut, le dossier de l’application est situé dans $HOME/AndroidStudioProjects
.
Le framework android.graphics divise le dessin en deux concepts :
Par exemple, Canvas fournit une méthode pour dessiner une ligne, tandis que Paint fournit des méthodes pour définir la couleur de cette ligne. Canvas a une méthode pour dessiner un rectangle, tandis que Paint définit s’il faut remplir ce rectangle avec une couleur ou le laisser vide.
La classe Canvas, qui joue un rôle central dans les graphiques 2D, contient les appels de dessin (“draw”). Pour dessiner quelque chose, on aura besoin de 4 composants de base :
drawColor()
, drawArc()
, drawRect()
, drawCircle()
et drawText()
, …), etVoir aussi : Rect, Path, … Color qui représente un code couleur comme un int. La classe Color
définit un certain nombre de méthodes pour créer et convertir des couleurs.
Pour dessiner à l’écran, on pourra créer une vue personnalisée en héritant de la classe View
ou l’une de ses sous-classes puis on redéfinira sa méthode onDraw()
. Cette méthode reçoit en paramètre un objet Canvas dans lequel on dessinera. Il suffira d’utiliser un objet Paint pour personnaliser son dessin.
Lien : Vue personnalisée
On définit les layouts :
activity_main.xml
:<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
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"
android:fitsSystemWindows="true"
tools:context="com.example.tv.myapplicationdessin.MainActivity">
<include layout="@layout/content_main"/>
</android.support.design.widget.CoordinatorLayout>
content_main.xml
:<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:id="@+id/top"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
android:orientation="vertical">
<TextView
android:id="@+id/textView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="15dp"
android:gravity="center"
android:text="Dessin 2D sous Android (tv)"
android:textSize="24sp"/>
</LinearLayout>
<LinearLayout
android:id="@+id/myView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_below="@+id/top"
android:gravity="center"
android:orientation="horizontal">
</LinearLayout>
</RelativeLayout>
On crée une activité principale dans laquelle on intègre une vue personnalisée associée au layout précédent :
package com.example.tv.myapplicationdessin;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.LinearLayout;
public class MainActivity extends AppCompatActivity
{
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
MyView myViewCompteur = new MyView(this);
LinearLayout myLayout1 = (LinearLayout)findViewById(R.id.myView);
myLayout1.addView(myViewCompteur);
setContentView(myViewCompteur);
}
}
On définit la vue personnalisée et on y dessine quelques formes classiques (lignes, cercles, textes, …) :
package com.example.tv.myapplicationdessin;
import android.view.View;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Rect;
import android.graphics.RectF;
public class MyView extends View
{
Paint paint = new Paint(Paint.FAKE_BOLD_TEXT_FLAG);
Path starPath;
Path curvePath;
Paint textPaint = new Paint(Paint.LINEAR_TEXT_FLAG);
public MyView(Context context)
{
super(context);
// create star path
starPath = createStarPath(300, 500);
curvePath = createCurvePath();
}
@Override
protected void onDraw(Canvas canvas)
{
// draw basic shapes
canvas.drawLine(5, 5, 200, 5, paint);
canvas.drawLine(5, 15, 200, 15, paint);
canvas.drawLine(5, 25, 200, 25, paint);
paint.setColor(Color.YELLOW);
canvas.drawCircle(50, 70, 35, paint);
paint.setColor(Color.GREEN);
canvas.drawRect(new Rect(100, 60, 150, 80), paint);
paint.setColor(Color.DKGRAY);
canvas.drawOval(new RectF(160, 60, 250, 80), paint);
// draw text
textPaint.setColor(Color.MAGENTA);
textPaint.setTextSize(40);
canvas.drawText("Hello World!", 20, 190, textPaint);
// transparency
textPaint.setColor(0xFF465574);
textPaint.setTextSize(60);
canvas.drawText("Android Rocks", 20, 340, textPaint);
// opaque circle
canvas.drawCircle(80, 300, 20, paint);
// semi-transparent circle
paint.setAlpha(110);
canvas.drawCircle(160, 300, 39, paint);
paint.setColor(Color.YELLOW);
paint.setAlpha(140);
canvas.drawCircle(240, 330, 30, paint);
paint.setColor(Color.MAGENTA);
paint.setAlpha(30);
canvas.drawCircle(288, 350, 30, paint);
paint.setColor(Color.CYAN);
paint.setAlpha(100);
canvas.drawCircle(380, 330, 50, paint);
// draw text on path
textPaint.setColor(Color.rgb(155, 20, 10));
canvas.drawTextOnPath("BTS SN LASALLE 84", curvePath, 10, 10, textPaint);
// create a star-shaped clip
canvas.drawPath(starPath, textPaint);
textPaint.setColor(Color.CYAN);
canvas.clipPath(starPath);
//textPaint.setColor(Color.parseColor("yellow"));
//canvas.drawText("Android", 350, 550, textPaint);
textPaint.setColor(Color.parseColor("#abde97"));
canvas.drawText("Android", 400, 600, textPaint);
canvas.drawText("Android Rocks", 300, 650, textPaint);
canvas.drawText("Android Rocks", 320, 700, textPaint);
canvas.drawText("Android Rocks", 360, 750, textPaint);
canvas.drawText("Android Rocks", 320, 800, textPaint);
paint.setColor(Color.BLACK);
paint.setStyle(Paint.Style.STROKE);
Rect r = canvas.getClipBounds();
canvas.drawRect(r, paint);
}
private Path createStarPath(int x, int y)
{
Path path = new Path();
path.moveTo(0 + x, 150 + y);
path.lineTo(120 + x, 140 + y);
path.lineTo(150 + x, 0 + y);
path.lineTo(180 + x, 140 + y);
path.lineTo(300 + x, 150 + y);
path.lineTo(200 + x, 190 + y);
path.lineTo(250 + x, 300 + y);
path.lineTo(150 + x, 220 + y);
path.lineTo(50 + x, 300 + y);
path.lineTo(100 + x, 190 + y);
path.lineTo(0 + x, 150 + y);
return path;
}
private Path createCurvePath()
{
Path path = new Path();
path.addArc(new RectF(400, 40, 780, 300), -210, 230);
return path;
}
}
On obtient :
Dans cet exemple, on va créer un objet Drawable
qui aura la capacité de se dessiner dans un Canvas. Ceci va permettre de séparer la vue (Drawable
) de l’objet à dessiner (View
). On va réaliser le dessin d’une boussole et d’un compteur de vitesse.
On va tout d’abord modifier les layouts pour y intégrer notamment une SeekBar
pour tester nos deux nouveaux widgets :
activity_main.xml
:<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
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"
android:fitsSystemWindows="true"
tools:context="com.example.tv.myapplicationdessin.MainActivity">
<include layout="@layout/content_main"/>
</android.support.design.widget.CoordinatorLayout>
content_main.xml
:<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:id="@+id/top"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
android:orientation="vertical">
<TextView
android:id="@+id/textView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="15dp"
android:gravity="center"
android:text="Dessin 2D sous Android (tv)"
android:textSize="24sp"/>
<SeekBar
android:id="@+id/seekBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:max="360"/>
</LinearLayout>
<LinearLayout
android:id="@+id/myView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignParentBottom="true"
android:layout_below="@+id/top"
android:gravity="center"
android:orientation="horizontal">
</LinearLayout>
</RelativeLayout>
On modifie ensuite l’activité principale pour y intégrer nos deux widgets :
package com.example.tv.myapplicationdessin;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.LinearLayout;
import android.widget.SeekBar;
public class MainActivity extends AppCompatActivity
{
SeekBar seekBar;
CompteurVitesse compteurVitesse;
Boussole boussole;
MyView myViewCompteur;
MyView myViewBoussole;
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
seekBar = (SeekBar)findViewById(R.id.seekBar);
seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
if(progress <= 270)
{
compteurVitesse.setVitesse((float) progress);
myViewCompteur.invalidate();
}
boussole.setDirection((float)progress);
myViewBoussole.invalidate();
System.out.println("<Dessin> progress : " + progress);
}
});
boussole = new Boussole(0);
compteurVitesse = new CompteurVitesse(0);
//compteurVitesse.setTaille(600);
LinearLayout myLayout1 = (LinearLayout)findViewById(R.id.myView);
myViewBoussole = new MyView(this, boussole);
myLayout1.addView(myViewBoussole);
myViewCompteur = new MyView(this, compteurVitesse);
myLayout1.addView(myViewCompteur);
}
}
La vue personnalisée a changé, elle reçoit maintenant en paramètre l’objet Drawable
à dessiner dans le Canvas
:
package com.example.tv.myapplicationdessin;
import android.view.View;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
public class MyView extends View
{
Paint paint = new Paint(Paint.FAKE_BOLD_TEXT_FLAG);
Path starPath;
Path curvePath;
Paint textPaint = new Paint(Paint.LINEAR_TEXT_FLAG);
private final Drawable drawable;
public MyView(Context context, Drawable draw)
{
super(context);
setMinimumWidth(600);
setMinimumHeight(600);
drawable = draw;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
setMeasuredDimension(getSuggestedMinimumWidth(), getSuggestedMinimumHeight());
}
@Override
protected void onDraw(Canvas canvas)
{
canvas.save();
drawable.draw(canvas);
canvas.restore();
// Un cadre
/*Paint paint = new Paint();
paint.setColor(Color.BLACK);
paint.setStyle(Paint.Style.STROKE);
Rect r = canvas.getClipBounds();
canvas.drawRect(r, paint);*/
}
}
Le widget Boussole :
package com.example.tv.myapplicationdessin;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PixelFormat;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
public class Boussole extends Drawable
{
private ColorFilter filter;
private int opacity;
private float direction;
private int cx;
private int cy;
private int lenght;
public Boussole(float degres)
{
direction = degres;
cx = 300;
cy = 300;
lenght = 550;
opacity = 100;
}
public void setDirection(float degres)
{
direction = degres;
}
@Override
public void draw(Canvas canvas)
{
Paint paint = new Paint();
paint.setColorFilter(filter);
paint.setAlpha(opacity);
paint.setTextSize(30);
canvas.save();
Path sudPath = creerSudPath();
Path nordPath = creerNordPath();
float fontHeight = paint.getFontMetrics().ascent + paint.getFontMetrics().descent;
// Centrer le compas au milieu de l'écran
canvas.translate(cx, cy);
// Effectuer une rotation de canvas, de sorte que la flèche indique le nord
// quand on la dessine verticalement
canvas.rotate(direction);
// Définir le cadran
paint.setColor(0xFFEEEEEE);
canvas.drawCircle(0, 0, (lenght+20) / 2, paint);
// Dessiner le cercle de la boussole
paint.setColor(Color.GRAY);
canvas.drawCircle(0, 0, lenght / 2, paint);
// Dessiner la graduation du compas
paint.setColor(Color.WHITE);
float hText = - lenght/2 - fontHeight+3;
// Tous les 15° faire une graduation
int step = 15;
for (int degree = 0; degree < 360; degree = degree + step)
{
// Si ce n'est pas un point cardinal, dessiner une graduation
if ((degree % 90) != 0)
{
canvas.drawText("|", 0, hText, paint);
}
canvas.rotate(-step);
}
// Dessiner les points cardinaux
canvas.drawText("N", 0, hText, paint);
canvas.rotate(-90);
canvas.drawText("W", 0, hText, paint);
canvas.rotate(-90);
canvas.drawText("S", 0, hText, paint);
canvas.rotate(-90);
canvas.drawText("E", 0, hText, paint);
canvas.rotate(-90);
// Dessiner les flèches
paint.setStyle(Paint.Style.FILL);
paint.setColor(Color.RED);
canvas.drawPath(nordPath, paint);
paint.setColor(Color.BLUE);
canvas.drawPath(sudPath, paint);
// Restaurer l'état initial du canvas
canvas.restore();
}
private Path creerSudPath()
{
Path sudPath = new Path();
sudPath.moveTo(-10,0);
sudPath.lineTo(0,lenght/3);
sudPath.lineTo(10,0);
sudPath.close();
return sudPath;
}
private Path creerNordPath()
{
Path nordPath = new Path();
nordPath.moveTo(0, -(lenght/3));
nordPath.lineTo(-10, 0);
nordPath.lineTo(10,0);
nordPath.close();
return nordPath;
}
@Override
public int getOpacity()
{
return PixelFormat.TRANSLUCENT;
}
@Override
public void setAlpha(int alpha)
{
}
@Override
public void setColorFilter(ColorFilter cf)
{
}
}
public class CompteurVitesse extends Drawable
{
private ColorFilter filter;
private int opacity;
private float vitesse;
private int taille;
private int cx;
private int cy;
private int longueur;
private int pas;
private int graduation;
...
}
On obtient :
Lien : Digression graphique sur mathias-seguy.developpez.com
Ici, on va utiliser la bibliothèque GraphView pour dessiner des graphiques dans une vue.
Liens :
Il faut tout d’abord ajouter compile 'com.jjoe64:graphview:4.2.1'
à votre fichier app/build.gradle
:
apply plugin: 'com.android.application'
android {
compileSdkVersion 25
buildToolsVersion "25.0.3"
defaultConfig {
applicationId "com.example.tv.myapplicationdessin"
minSdkVersion 16
targetSdkVersion 25
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
exclude group: 'com.android.support', module: 'support-annotations'
})
compile 'com.jjoe64:graphview:4.2.1'
compile 'com.android.support:appcompat-v7:25.3.1'
compile 'com.android.support:design:25.3.1'
compile 'com.android.support.constraint:constraint-layout:1.0.2'
testCompile 'junit:junit:4.12'
}
Ensuite, il faut ajouter une vue GraphView
dans le layout :
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:id="@+id/top"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
android:orientation="vertical">
<TextView
android:id="@+id/textView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="15dp"
android:gravity="center"
android:text="Graphique sous Android (tv)"
android:textSize="24sp"/>
<SeekBar
android:id="@+id/seekBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:max="100"/>
</LinearLayout>
<com.jjoe64.graphview.GraphView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignParentBottom="true"
android:layout_below="@+id/top"
android:gravity="center"
android:id="@+id/graph" />
</RelativeLayout>
Pour terminer, on dessine un graphique à partir de l’activité principale :
package com.example.tv.myapplicationgraph;
import android.graphics.Color;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.SeekBar;
import com.jjoe64.graphview.GraphView;
import com.jjoe64.graphview.LegendRenderer;
import com.jjoe64.graphview.series.BarGraphSeries;
import com.jjoe64.graphview.series.DataPoint;
import com.jjoe64.graphview.series.LineGraphSeries;
public class MainActivity extends AppCompatActivity
{
SeekBar seekBar;
GraphView graph;
private LineGraphSeries<DataPoint> mSeries;
private double graphLastXValue = -1d;
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
seekBar = (SeekBar)findViewById(R.id.seekBar);
seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
graphLastXValue += 1d;
mSeries.appendData(new DataPoint(graphLastXValue, progress), true, 40);
System.out.println("<Dessin> progress : " + progress);
}
});
graph = (GraphView) findViewById(R.id.graph);
// Exemple 1
/*LineGraphSeries<DataPoint> series = new LineGraphSeries<DataPoint>(new DataPoint[] {
new DataPoint(0, 1),
new DataPoint(1, 5),
new DataPoint(2, 3),
new DataPoint(3, 2),
new DataPoint(4, 6)
});
graph.addSeries(series);*/
// Exemple 2
/*BarGraphSeries<DataPoint> series = new BarGraphSeries<>(new DataPoint[] {
new DataPoint(0, 1),
new DataPoint(1, 5),
new DataPoint(2, 3),
new DataPoint(3, 2),
new DataPoint(4, 6)
});
graph.addSeries(series);*/
// Exemple 3 : avec la seekbar
graph.setTitle("Un graphique");
graph.setTitleTextSize(40);
graph.setTitleColor(Color.BLUE);
mSeries = new LineGraphSeries<>();
graph.addSeries(mSeries);
graph.getViewport().setYAxisBoundsManual(true);
graph.getViewport().setXAxisBoundsManual(true);
graph.getViewport().setMinY(0);
graph.getViewport().setMaxY(100);
graph.getViewport().setMinX(0);
graph.getViewport().setMaxX(40);
// Légende
mSeries.setTitle("SeekBar");
graph.getLegendRenderer().setVisible(true);
graph.getLegendRenderer().setAlign(LegendRenderer.LegendAlign.TOP);
// Zooming and scrolling
//graph.getViewport().setScalable(true); // enables horizontal zooming and scrolling
//graph.getViewport().setScalableY(true); // enables vertical zooming and scrolling
//graph.getViewport().setScrollable(true); // enables horizontal scrolling
//graph.getViewport().setScrollableY(true); // enables vertical scrolling
}
}
On obtient (l’exemple 3 avec la seekbar) :