Breaking News

Android Barcode / QR Code Scanner using Google Mobile Vision – Building Movie Tickets App

Saat ini Barcode dan QR Codes banyak digunakan di banyak aplikasi mobile. Dalam QR Code Anda bisa menyimpan informasi seperti teks, sms, email, url, image, audio dan beberapa format lainnya. Di Android Anda bisa mengekstrak informasi yang tersimpan di barcode dengan menggunakan Google Vision Library. Meskipun ada banyak perpustakaan lain yang tersedia, perpustakaan penglihatan google paling baik dipertimbangkan karena tidak hanya menyediakan pembacaan kode batang tetapi juga memiliki fitur lain seperti deteksi wajah, pendeteksian teks.



Pada artikel ini kita akan belajar bagaimana menggunakan perpustakaan google vision dengan membuat aplikasi pemindaian tiket film sederhana.
  1. Google Mobile Vision API
    API Google Mobile Vision membantu menemukan objek dalam gambar atau video. Ini menyediakan fungsionalitas seperti deteksi wajah, deteksi teks dan deteksi barcode. Semua fungsi ini bisa digunakan secara terpisah atau digabungkan bersama.

    Artikel ini bertujuan untuk menjelaskan deteksi barcode dengan skenario use case realtime. Kita bisa melihat banyak aplikasi pemindaian barcode yang digunakan di supermarket, bioskop dan hotel yang memindai kode batang dan memberi informasi yang diinginkan kepada pengguna. Pada artikel ini kami akan mencoba membuat aplikasi pemindai tiket film sederhana yang deteksi barcode / qrcode dan menampilkan informasi film untuk memesan tiket.

    The google vision library adalah bagian dari layanan bermain dan dapat ditambahkan ke build.gradle.

    compile 'com.google.android.gms:play-services-vision:11.0.2'
    
  2. Barcode Scanner Library
    Google menyediakan tutorial sederhana untuk mencoba perpustakaan deteksi barcode dengan gambar bitmap sederhana. Tapi ketika harus memindai kamera realtime untuk barcode, hal ini menjadi sulit untuk diterapkan karena kita perlu melakukan deteksi barcode pada video kamera.
  3. How to Use the Barcode library
    Ikuti langkah-langkah sederhana berikut untuk menyertakan library barcode / qrcode di proyek Anda.
    1. Tambahkan pembaca barcode androidhive dan perpustakaan penglihatan google ke file build.gradle aplikasi Anda.
    Artikel ini ditulis menggunakan Android Studio 3.0 Canary 9. Jika sudah tidak berlaku lagi, bisa diganti dengan keadaan aktualnya
    dependencies {
        // barcode reader library
        implementation 'info.androidhive:barcode-reader:1.1.5'
     
        // google vision library
        implementation 'com.google.android.gms:play-services-vision:11.0.2'
    }
    

    2. Tambahkan fragmen kamera barcode ke aktivitas atau fragmen Anda.

    <fragment
            android:id="@+id/barcode_scanner"
            android:name="info.androidhive.barcode.BarcodeReader"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:auto_focus="true"
            app:use_flash="false" />
    

    3. Terapkan Aktivitas Anda dari BarcodeReader.BarcodeReaderListener dan timpa metode yang diperlukan.

    import android.support.v7.app.AppCompatActivity;
    import android.os.Bundle;
    import android.util.SparseArray;
    import com.google.android.gms.vision.barcode.Barcode;
    import java.util.List;
     
    import info.androidhive.barcode.BarcodeReader;
     
    public class MainActivity extends AppCompatActivity implements BarcodeReader.BarcodeReaderListener {
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_scan);
        }
     
        @Override
        public void onScanned(Barcode barcode) {
            // single barcode scanned
        }
     
        @Override
        public void onScannedMultiple(List<Barcode> list) {
            // multiple barcodes scanned
        }
     
        @Override
        public void onBitmapScanned(SparseArray<Barcode> sparseArray) {
            // barcode scanned from bitmap image
        }
     
        @Override
        public void onScanError(String s) {
            // scan error
        }
     
        @Override
        public void onCameraPermissionDenied() {
            // camera permission denied
        }
    }
    

    4. Jalankan proyek Anda dan cobalah memindai kode batang atau qrcode. Hasil pindaian akan dikembalikan pada metode onScanned () atau onScannedMultiple ().

    3.1. Adding Scanning Overlay Indicator LineKita dapat melihat semua aplikasi pemindai umumnya menambahkan garis indikator pada hamparan kamera untuk menunjukkan kemajuan pemindaian yang sedang berlangsung. Untuk mencapai hal ini, saya telah menambahkan kelas yang dapat digunakan kembali di perpustakaan yang sama yang dapat ditambahkan ke layar kamera.

    Untuk menambahkan baris pemindaian animasi, tambahkan kode info.androidhive.barcode.ScannerOverlay ke aktivitas yang sama tumpang tindih dengan fragmen kamera.

    <info.androidhive.barcode.ScannerOverlay
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="#44000000"
        app:line_color="#7323DC"
        app:line_speed="6"
        app:line_width="4"
        app:square_height="200"
        app:square_width="200"/>
    

    Perpustakaan juga berisi beberapa fungsi berguna lainnya seperti lampu kilat otomatis, bunyi bip dll, Informasi terperinci tentang perpustakaan dapat ditemukan di halaman Github-nya.

  4. Membuat Proyek Baru - Membangun Aplikasi Pemindaian Tiket Sederhana
    Karena perpustakaan pemindaian barcode sudah tersedia, kita akan melihat bagaimana menggunakan perpustakaan dengan mempertimbangkan skenario penggunaan nyata. Aplikasi yang akan kami bangun tidak hanya menjelaskan pemindaian kode batang, namun juga mencakup pengembangan UI kompleks, membuat REST api memanggil untuk mengambil film json dan menulis kelas tampilan khusus.

    Secara keseluruhan aplikasi berisi tiga layar. Yang pertama adalah splash screen, kedua adalah barcode scanning dan yang terakhir adalah untuk menampilkan informasi tiket film.

    Berikut adalah tangkapan layar aplikasi.

    4.1 REST API
    Untuk membangun aplikasi ini, kita memerlukan REST API untuk mencari melalui database film dengan kode batang film. Saya telah menulis sebuah restorasi sederhana untuk mencari film dengan kode batang. Ini mengambil nilai kode bar standar sebagai parameter kueri dan mencarinya di db. Berikut adalah beberapa contoh kode batang untuk pengujian.

    1. dn_barcode.jpg
    2. spiderman_barcode.jpg
    3. wonderwoman_barcode.jpg
    4. dunkirk_barcode.jpg


    Search Movie:
    Minta GET permintaan dengan nilai kode batangnya dibaca.
    https://api.androidhive.info/barcodes/search.php?code=dunkirk

    Movie Result:
    Film json yang cocok akan diberikan dalam responnya.

    {
        "name": "Dunkirk",
        "poster": "https://api.androidhive.info/barcodes/dunkirk.jpg",
        "duration": "1hr 46min",
        "rating": 4.6,
        "released": true,
        "genre": "Action",
        "price": "₹200",
        "director": "Christopher Nolan"
    }
    

    Sekarang kita memiliki semua informasi yang diperlukan bersama kita. Mari kita mulai dengan membuat proyek baru di Android Studio.
    Catatan: Proyek ini dikembangkan menggunakan Android Studio 3.0 Canary 9
    1. Buat proyek baru di Android Studio dari File ⇒ New Project dan isi rincian proyek. Saya memberi nama proyek sebagai MovieTickets dan nama paketnya sebagai info.androidhive.movietickets
    2. Buka app build.gradle dan tambahkan barcode dan google vision dependencies. Saya juga menambahkan Glide, Volley dan Gson perpustakaan karena mereka diminta untuk membuat panggilan http, json parsing dan tampilan gambar.

    dependencies {
        implementation 'com.google.android.gms:play-services-vision:11.0.2'
     
        // vision barcode scanner
        implementation 'info.androidhive:barcode-reader:1.1.2'
     
        // glide image library
        implementation 'com.github.bumptech.glide:glide:4.0.0-RC1'
        annotationProcessor 'com.github.bumptech.glide:compiler:4.0.0-RC1'
     
        implementation 'com.android.volley:volley:1.0.0'
        implementation 'com.google.code.gson:gson:2.6.2'
    }

    3. Tambahkan string, dimen, color resources ke masing-masing file di bawah direktori res.

    string.xml
    <resources>
        <string name="app_name">Movie Tickets</string>
        <string name="title_activity_ticket">Book Ticket</string>
        <string name="lbl_duration">DURATION</string>
        <string name="lbl_genre">GENRE</string>
        <string name="lbl_rating">RATING</string>
        <string name="lbl_price">PRICE</string>
        <string name="btn_buy_now">BUY NOW</string>
        <string name="btn_coming_soon">COMING SOON</string>
        <string name="msg_no_ticket_found">No ticket found. Try scanning the QR Codes from http://api.androidhive.info/qrcodes/</string>
    </resources>
    

    dimen.xml
    <?xml version="1.0" encoding="utf-8"?>
    <resources>
        <dimen name="dimen_20">20dp</dimen>
        <dimen name="dimen_10">10dp</dimen>
        <dimen name="activity_margin">16dp</dimen>
        <dimen name="lbl_directory">14dp</dimen>
        <dimen name="lbl_movie_name">28dp</dimen>
        <dimen name="img_poster_height">220dp</dimen>
        <dimen name="dimen_40">40dp</dimen>
    </resources>
    

    color.xml
    <?xml version="1.0" encoding="utf-8"?>
    <resources>
        <color name="colorPrimary">#6d0094</color>
        <color name="colorPrimaryDark">#6d0094</color>
        <color name="colorAccent">#ff2068</color>
        <color name="colorAccentSecondary">#ad1a7f</color>
        <color name="viewBg">#f8f8f8</color>
        <color name="btn_disabled">#999</color>
        <color name="lbl_value">#222222</color>
    </resources>
    

    4. Buat kelas bernama MyApplication.java dan tambahkan kode di bawah ini. Disini kita buat contoh volley singleton.
    Volley tidak disarankan untuk membuat panggilan http, tapi untuk membuat integrasi sederhana, ini dipertimbangkan dalam tutorial ini. Pertimbangkan untuk menggunakan Retrofit di aplikasi produksi Anda.
    MyAplication.java
    import android.app.Application;
    import android.text.TextUtils;
     
    import com.android.volley.Request;
    import com.android.volley.RequestQueue;
    import com.android.volley.toolbox.Volley;
     
    /**
     * Created by ravi on 31/07/17.
     */
     
    public class MyApplication extends Application {
     
        public static final String TAG = MyApplication.class
                .getSimpleName();
     
        private RequestQueue mRequestQueue;
     
        private static MyApplication mInstance;
     
        @Override
        public void onCreate() {
            super.onCreate();
            mInstance = this;
        }
     
        public static synchronized MyApplication getInstance() {
            return mInstance;
        }
     
        public RequestQueue getRequestQueue() {
            if (mRequestQueue == null) {
                mRequestQueue = Volley.newRequestQueue(getApplicationContext());
            }
     
            return mRequestQueue;
        }
     
        public <T> void addToRequestQueue(Request<T> req, String tag) {
            // set the default tag if tag is empty
            req.setTag(TextUtils.isEmpty(tag) ? TAG : tag);
            getRequestQueue().add(req);
        }
     
        public <T> void addToRequestQueue(Request<T> req) {
            req.setTag(TAG);
            getRequestQueue().add(req);
        }
     
        public void cancelPendingRequests(Object tag) {
            if (mRequestQueue != null) {
                mRequestQueue.cancelAll(tag);
            }
        }
    }
    

    5. Buka AndroidManifest.xml dan tambahkan kelas MyApplication ke tag <applicaton>. Tambahkan juga izin INTERNET karena kami perlu melakukan panggilan http.

    AndroidManifest.xml
    <?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="info.androidhive.movietickets">
     
        <uses-permission android:name="android.permission.INTERNET" />
     
        <application
            android:name=".MyApplication"
            android:allowBackup="true"
            android:icon="@mipmap/ic_launcher"
            android:label="@string/app_name"
            android:roundIcon="@mipmap/ic_launcher_round"
            android:supportsRtl="true"
            android:theme="@style/AppTheme">
            <activity
                android:name=".MainActivity"
                android:screenOrientation="portrait"
                android:theme="@style/AppTheme.NoActionBar">
                <intent-filter>
                    <action android:name="android.intent.action.MAIN" />
     
                    <category android:name="android.intent.category.LAUNCHER" />
                </intent-filter>
            </activity>
            <activity
                android:name=".ScanActivity"
                android:screenOrientation="portrait" />
            <activity
                android:name=".TicketActivity"
                android:label="@string/title_activity_ticket"
                android:screenOrientation="portrait"
                android:theme="@style/AppTheme.NoActionBar"></activity>
        </application>
    </manifest>
    

    6. Pada folder res ⇒ drawable buat xml drawable yang diberi nama bg_gradient.xml Yang menarik ini memberikan latar belakang gradien untuk dilihat.

    bg_gradient.xml
    <?xml version="1.0" encoding="utf-8"?>
    <shape xmlns:android="http://schemas.android.com/apk/res/android"
        android:shape="rectangle">
        <gradient
            android:angle="135"
            android:centerColor="@color/colorAccentSecondary"
            android:endColor="@color/colorPrimary"
            android:startColor="@color/colorAccent"
            android:type="linear" />
     
        <corners android:radius="0dp" />
    </shape>

    4.2 Menambahkan Landing Screen
    Layar pendaratan akan memiliki beberapa bidang teks dan tombol untuk membuka kamera pemindai. Hal menarik yang akan Anda pelajari di sini adalah memberikan latar belakang aktivitas yang gradien.

    7. Buka file layout aktivitas utama anda (activity_main.xml) tambahkan layout di bawah ini. Disini kita menambahkan tombol untuk memulai aktivitas pemotretan.

    <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout 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:background="@drawable/bg_gradient"
        tools:context="info.androidhive.movietickets.MainActivity">
     
        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerHorizontal="true"
            android:layout_centerInParent="true"
            android:gravity="center"
            android:orientation="vertical"
            android:paddingLeft="40dp"
            android:paddingRight="40dp">
     
            <ImageView
                android:id="@+id/icon"
                android:layout_width="100dp"
                android:layout_height="100dp"
                android:layout_centerHorizontal="true"
                android:clickable="true"
                android:foreground="?attr/selectableItemBackground"
                android:src="@drawable/qrcode"
                android:tint="@android:color/white" />
     
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="30dp"
                android:fontFamily="sans-serif-light"
                android:gravity="center"
                android:text="Scan the QR code on the poster and book your movie tickets"
                android:textColor="@android:color/white"
                android:textSize="16dp" />
        </LinearLayout>
     
        <Button
            android:id="@+id/btn_scan"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentBottom="true"
            android:layout_centerHorizontal="true"
            android:layout_marginBottom="40dp"
            android:background="@android:color/transparent"
            android:foreground="?attr/selectableItemBackground"
            android:paddingLeft="20dp"
            android:paddingRight="20dp"
            android:fontFamily="sans-serif-medium"
            android:text="Scan QR Code"
            android:textColor="@android:color/white"
            android:textSize="18sp" />
     
    </RelativeLayout>
    

    8. Buka aktivitas utama dan lakukan perubahan yang diperlukan di bawah ini. Dalam hal ini metode transparentToolbar () membuat toolbar transparan. Pendengar klik tombol akan meluncurkan aktivitas pemindai yang akan segera kita buat.

    MainActivity.java
    import android.app.Activity;
    import android.content.Intent;
    import android.graphics.Color;
    import android.os.Build;
    import android.support.v7.app.AppCompatActivity;
    import android.os.Bundle;
    import android.util.SparseArray;
    import android.view.View;
    import android.view.Window;
    import android.view.WindowManager;
     
    import com.google.android.gms.vision.barcode.Barcode;
     
    import java.util.List;
     
    import info.androidhive.barcode.BarcodeReader;
     
    public class MainActivity extends AppCompatActivity {
     
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
     
            // making toolbar transparent
            transparentToolbar();
     
            setContentView(R.layout.activity_main);
     
            findViewById(R.id.btn_scan).setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    startActivity(new Intent(MainActivity.this, ScanActivity.class));
                }
            });
        }
     
        private void transparentToolbar() {
            if (Build.VERSION.SDK_INT >= 19 && Build.VERSION.SDK_INT < 21) {
                setWindowFlag(this, WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS, true);
            }
            if (Build.VERSION.SDK_INT >= 19) {
                getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
            }
            if (Build.VERSION.SDK_INT >= 21) {
                setWindowFlag(this, WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS, false);
                getWindow().setStatusBarColor(Color.TRANSPARENT);
            }
        }
     
        private void setWindowFlag(Activity activity, final int bits, boolean on) {
            Window win = activity.getWindow();
            WindowManager.LayoutParams winParams = win.getAttributes();
            if (on) {
                winParams.flags |= bits;
            } else {
                winParams.flags &= ~bits;
            }
            win.setAttributes(winParams);
        }
    }
    

    Jika Anda menjalankan aplikasi, Anda dapat melihat layar pendaratan seperti di bawah ini.

    4.3 Menambahkan Layar Scan Tiket
    Sekarang kita sudah siapkan layar pendaratan. Ayo buat aktivitas pemindai.

    9. Buat aktivitas baru dari File ⇒ New ⇒ Activity ⇒ Empty Activity dan beri nama ScanActivity.java

    10. Buka file layout aktivitas ticket (activity_scan.xml) dan tambahkan fragmen Barcode Reader seperti di bawah ini. Di sini kita juga menambahkan tampilan animasi indikator garis pemindai.

    activity_scan.xml
    <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout 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="info.androidhive.movietickets.ScanActivity">
     
        <fragment
            android:id="@+id/barcode_scanner"
            android:name="info.androidhive.barcode.BarcodeReader"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:auto_focus="true"
            app:use_flash="false" />
     
        <info.androidhive.barcode.ScannerOverlay
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="#44000000"
            app:line_color="#7323DC"
            app:line_speed="6"
            app:line_width="4"
            app:square_height="200"
            app:square_width="200" />
     
    </RelativeLayout>
    

    11. Buka ScanActivity.java dan lakukan perubahan yang diperlukan di bawah ini.
    > Terapkan aktivitas dari BarcodeReader.BarcodeReaderListener
    > Ganti metode callback onScanned (), onScannedMultiple () dan callback lainnya.
    > barcodeReader.playBeep () memainkan suara bip pada kode batang dibaca.
    > Luncurkan TicketActivity setelah kode batang dipindai dengan melewatkan nilai kode bar dengan maksud.

    ScanActivity.java
    package info.androidhive.movietickets;
     
    import android.content.Intent;
    import android.support.v7.app.AppCompatActivity;
    import android.os.Bundle;
    import android.util.SparseArray;
    import android.widget.Toast;
     
    import com.google.android.gms.vision.barcode.Barcode;
     
    import java.util.List;
     
    import info.androidhive.barcode.BarcodeReader;
     
    public class ScanActivity extends AppCompatActivity implements BarcodeReader.BarcodeReaderListener {
     
        BarcodeReader barcodeReader;
     
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_scan);
     
            // get the barcode reader instance
            barcodeReader = (BarcodeReader) getSupportFragmentManager().findFragmentById(R.id.barcode_scanner);
        }
     
        @Override
        public void onScanned(Barcode barcode) {
     
            // playing barcode reader beep sound
            barcodeReader.playBeep();
     
            // ticket details activity by passing barcode
            Intent intent = new Intent(ScanActivity.this, TicketActivity.class);
            intent.putExtra("code", barcode.displayValue);
            startActivity(intent);
        }
     
        @Override
        public void onScannedMultiple(List<Barcode> list) {
     
        }
     
        @Override
        public void onBitmapScanned(SparseArray<Barcode> sparseArray) {
     
        }
     
        @Override
        public void onCameraPermissionDenied() {
            finish();
        }
     
        @Override
        public void onScanError(String s) {
            Toast.makeText(getApplicationContext(), "Error occurred while scanning " + s, Toast.LENGTH_SHORT).show();
        }
    }
    

    Jalankan aplikasi dan coba pindai kode batang apa saja. Kode batang yang dipindai akan dikembalikan pada metode onScanned ().

    4.4 Menambahkan Layar Hasil Pemindaian TiketLayar selanjutnya akan menjadi tiket hasil aktivitas. Di layar ini rincian film akan ditampilkan dengan mengirim pindaian pindaian untuk mencari titik akhir dan mendapatkan hasilnya.

    Jika Anda mengamati disainnya, saya telah menempatkan detail film pada tampilan tiket yang akan menancapkan lubang di tikungan. Untuk mencapai hal ini saya telah membuat custom view dan membuat lubang menggunakan penghapus.

    4.4.1 Preparing Ticket View with Punching Holes

    12. Buat kelas bernama TicketView.java. Ini adalah kelas pandangan biasa di mana Kanvas digunakan untuk membuat tampilan dengan lubang transparan di sudutnya.

    TicketView.java
    import android.content.Context;
    import android.graphics.Bitmap;
    import android.graphics.Canvas;
    import android.graphics.Color;
    import android.graphics.DashPathEffect;
    import android.graphics.Paint;
    import android.graphics.Path;
    import android.graphics.PorterDuff;
    import android.graphics.PorterDuffXfermode;
    import android.util.AttributeSet;
    import android.widget.LinearLayout;
     
    /**
     * Created by ravi tamada on 29/07/17.
     * Ticket view creates view with view punches making circular holes
     * in the view
     */
     
    public class TicketView extends LinearLayout {
        private Bitmap bm;
        private Canvas cv;
        private Paint eraser;
        private int holesBottomMargin = 70;
        private int holeRadius = 40;
     
        public TicketView(Context context) {
            super(context);
            Init();
        }
     
        public TicketView(Context context, AttributeSet attrs) {
            super(context, attrs);
            Init();
        }
     
        public TicketView(Context context, AttributeSet attrs,
                          int defStyleAttr) {
            super(context, attrs, defStyleAttr);
            Init();
        }
     
        private void Init() {
            eraser = new Paint();
            eraser.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
            eraser.setAntiAlias(true);
        }
     
        @Override
        protected void onSizeChanged(int w, int h, int oldw, int oldh) {
            if (w != oldw || h != oldh) {
                bm = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
                cv = new Canvas(bm);
            }
            super.onSizeChanged(w, h, oldw, oldh);
        }
     
        @Override
        protected void onDraw(Canvas canvas) {
            int w = getWidth();
            int h = getHeight();
     
            bm.eraseColor(Color.TRANSPARENT);
     
            // set the view background color
            cv.drawColor(Color.WHITE);
     
            // drawing footer square contains the buy now button
            Paint paint = new Paint();
            paint.setARGB(255, 250, 250, 250);
            paint.setStrokeWidth(0);
            paint.setStyle(Paint.Style.FILL);
            cv.drawRect(0, h, w, h - pxFromDp(getContext(), holesBottomMargin), paint);
     
            // adding punching holes on the ticket by erasing them
            cv.drawCircle(0, 0, holeRadius, eraser); // top-left hole
            cv.drawCircle(w / 2, 0, holeRadius, eraser); // top-middle hole
            cv.drawCircle(w, 0, holeRadius, eraser); // top-right
            cv.drawCircle(0, h - pxFromDp(getContext(), holesBottomMargin), holeRadius, eraser); // bottom-left hole
            cv.drawCircle(w, h - pxFromDp(getContext(), holesBottomMargin), holeRadius, eraser); // bottom right hole
     
            // drawing the image
            canvas.drawBitmap(bm, 0, 0, null);
     
     
            // drawing dashed lines at the bottom
            Path mPath = new Path();
            mPath.moveTo(holeRadius, h - pxFromDp(getContext(), holesBottomMargin));
            mPath.quadTo(w - holeRadius, h - pxFromDp(getContext(), holesBottomMargin), w - holeRadius, h - pxFromDp(getContext(), holesBottomMargin));
     
            // dashed line
            Paint dashed = new Paint();
            dashed.setARGB(255, 200, 200, 200);
            dashed.setStyle(Paint.Style.STROKE);
            dashed.setStrokeWidth(2);
            dashed.setPathEffect(new DashPathEffect(new float[]{10, 5}, 0));
            canvas.drawPath(mPath, dashed);
     
            super.onDraw(canvas);
        }
     
        public static float pxFromDp(final Context context, final float dp) {
            return dp * context.getResources().getDisplayMetrics().density;
        }
    }
    

    13. Buat kelas baru bernama TicketResultActivity.java dengan menavigasi ke File ⇒ New ⇒ Activity ⇒ Empty Activity.

    14. Buka tata letak aktivitas hasil tiket (activity_ticket_result.xml) dan tambahkan tata letak di bawah ini.

    activity_ticket_result.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:id="@+id/main_content"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@drawable/bg_gradient"
        android:descendantFocusability="beforeDescendants"
        android:fitsSystemWindows="true"
        tools:context=".TicketResultActivity">
     
        <android.support.design.widget.AppBarLayout
            foreground="?android:windowContentOverlay"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:theme="@style/AppTheme.AppBarOverlay">
     
     
            <android.support.v7.widget.Toolbar
                android:id="@+id/toolbar"
                android:layout_width="match_parent"
                android:layout_height="?actionBarSize"
                android:background="@color/colorPrimary"
                app:popupTheme="@style/AppTheme.PopupOverlay"></android.support.v7.widget.Toolbar>
     
        </android.support.design.widget.AppBarLayout>
     
        <include layout="@layout/content_ticket_details"/>
     
    </android.support.design.widget.CoordinatorLayout>

    content_ticket_detail.xml
    <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout 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:background="@drawable/bg_gradient"
        app:layout_behavior="@string/appbar_scrolling_view_behavior"
        tools:context=".TicketResultActivity">
     
        <TextView
            android:id="@+id/txt_error"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerHorizontal="true"
            android:layout_centerInParent="true"
            android:gravity="center_horizontal"
            android:text="@string/msg_no_ticket_found"
            android:textColor="@android:color/white"
            android:padding="@dimen/dimen_20"
            android:textSize="16dp"
            android:visibility="gone" />
     
        <info.androidhive.movietickets.TicketView
            android:id="@+id/layout_ticket"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_marginLeft="@dimen/dimen_20"
            android:layout_marginRight="@dimen/dimen_20"
            android:layout_marginTop="@dimen/dimen_20"
            android:background="@android:color/transparent"
            android:orientation="vertical"
            android:paddingTop="@dimen/dimen_10"
            android:visibility="gone">
     
            <TextView
                android:id="@+id/director"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginBottom="5dp"
                android:fontFamily="sans-serif-condensed"
                android:paddingLeft="@dimen/activity_margin"
                android:paddingRight="@dimen/activity_margin"
                android:paddingTop="@dimen/activity_margin"
                android:textAllCaps="true"
                android:textSize="@dimen/lbl_directory" />
     
            <TextView
                android:id="@+id/name"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:fontFamily="sans-serif-condensed"
                android:paddingLeft="16dp"
                android:paddingRight="16dp"
                android:textAllCaps="true"
                android:textColor="#111"
                android:maxLines="1"
                android:ellipsize="end"
                android:textSize="@dimen/lbl_movie_name" />
     
            <ImageView
                android:id="@+id/poster"
                android:layout_width="match_parent"
                android:layout_height="@dimen/img_poster_height"
                android:layout_marginBottom="@dimen/activity_margin"
                android:layout_marginTop="@dimen/activity_margin"
                android:scaleType="centerCrop" />
     
            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:orientation="horizontal"
                android:paddingLeft="@dimen/activity_margin"
                android:paddingRight="@dimen/activity_margin"
                android:weightSum="2">
     
                <LinearLayout
                    android:layout_width="0dp"
                    android:layout_height="wrap_content"
                    android:layout_weight="1"
                    android:orientation="vertical">
     
                    <TextView
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content"
                        android:layout_marginBottom="5dp"
                        android:fontFamily="sans-serif-condensed"
                        android:text="@string/lbl_duration"
                        android:textSize="12dp" />
     
                    <TextView
                        android:id="@+id/duration"
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content"
                        android:fontFamily="sans-serif-condensed"
                        android:textColor="@color/lbl_value"
                        android:textSize="22dp" />
                </LinearLayout>
     
                <LinearLayout
                    android:layout_width="0dp"
                    android:layout_height="wrap_content"
                    android:layout_weight="1"
                    android:orientation="vertical">
     
                    <TextView
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content"
                        android:layout_marginBottom="5dp"
                        android:fontFamily="sans-serif-condensed"
                        android:text="@string/lbl_genre"
                        android:textSize="12dp" />
     
                    <TextView
                        android:id="@+id/genre"
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content"
                        android:fontFamily="sans-serif-condensed"
                        android:textAllCaps="true"
                        android:textColor="@color/lbl_value"
                        android:textSize="22dp" />
                </LinearLayout>
     
            </LinearLayout>
     
            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginTop="16dp"
                android:orientation="horizontal"
                android:paddingLeft="16dp"
                android:paddingRight="16dp"
                android:weightSum="2">
     
                <LinearLayout
                    android:layout_width="0dp"
                    android:layout_height="wrap_content"
                    android:layout_weight="1"
                    android:orientation="vertical">
     
                    <TextView
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content"
                        android:layout_marginBottom="5dp"
                        android:fontFamily="sans-serif-condensed"
                        android:text="@string/lbl_rating"
                        android:textSize="12dp" />
     
                    <TextView
                        android:id="@+id/rating"
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content"
                        android:fontFamily="sans-serif-condensed"
                        android:textColor="@color/lbl_value"
                        android:textSize="22dp" />
                </LinearLayout>
     
                <LinearLayout
                    android:layout_width="0dp"
                    android:layout_height="wrap_content"
                    android:layout_weight="1"
                    android:orientation="vertical">
     
                    <TextView
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content"
                        android:layout_marginBottom="5dp"
                        android:fontFamily="sans-serif-condensed"
                        android:text="@string/lbl_price"
                        android:textSize="12dp" />
     
                    <TextView
                        android:id="@+id/price"
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content"
                        android:fontFamily="sans-serif-condensed"
                        android:textAllCaps="true"
                        android:textColor="@color/lbl_value"
                        android:textSize="22dp" />
                </LinearLayout>
     
            </LinearLayout>
     
        </info.androidhive.movietickets.TicketView>
     
        <Button
            android:id="@+id/btn_buy"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentBottom="true"
            android:layout_centerHorizontal="true"
            android:layout_marginBottom="10dp"
            android:background="@android:color/transparent"
            android:fontFamily="sans-serif-condensed"
            android:foreground="?attr/selectableItemBackground"
            android:paddingLeft="@dimen/activity_margin"
            android:paddingRight="@dimen/activity_margin"
            android:textColor="@color/colorPrimary"
            android:textSize="26dp" />
     
        <ProgressBar
            android:id="@+id/progressBar"
            android:layout_width="@dimen/dimen_40"
            android:layout_height="@dimen/dimen_40"
            android:layout_centerInParent="true"
            android:indeterminateTint="@android:color/white"
            android:indeterminateTintMode="src_atop"
            android:visibility="visible" />
    </RelativeLayout>

    15. Buka masing-masing TicketResultActivity.java dan ubah kodenya seperti di bawah ini.

    > searchBarcode () membuat volley http call untuk mencari endpoint dengan melewatkan kode barcode.
    > renderMovie () mem-parsing json dan membuat rincian film di layar.

    TicketResultActivity.java
    import android.os.Bundle;
    import android.support.v4.content.ContextCompat;
    import android.support.v7.app.AppCompatActivity;
    import android.support.v7.widget.Toolbar;
    import android.text.TextUtils;
    import android.util.Log;
    import android.view.MenuItem;
    import android.view.View;
    import android.widget.Button;
    import android.widget.ImageView;
    import android.widget.ProgressBar;
    import android.widget.TextView;
    import android.widget.Toast;
     
    import com.android.volley.Request;
    import com.android.volley.Response;
    import com.android.volley.VolleyError;
    import com.android.volley.toolbox.JsonObjectRequest;
    import com.bumptech.glide.Glide;
    import com.google.gson.Gson;
    import com.google.gson.JsonSyntaxException;
    import com.google.gson.annotations.SerializedName;
     
    import org.json.JSONObject;
     
    public class TicketResultActivity extends AppCompatActivity {
        private static final String TAG = TicketResultActivity.class.getSimpleName();
     
        // url to search barcode
        private static final String URL = "https://api.androidhive.info/barcodes/search.php?code=";
     
        private TextView txtName, txtDuration, txtDirector, txtGenre, txtRating, txtPrice, txtError;
        private ImageView imgPoster;
        private Button btnBuy;
        private ProgressBar progressBar;
        private TicketView ticketView;
     
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_ticket_result);
     
            Toolbar toolbar = findViewById(R.id.toolbar);
            setSupportActionBar(toolbar);
            getSupportActionBar().setDisplayHomeAsUpEnabled(true);
     
            txtName = findViewById(R.id.name);
            txtDirector = findViewById(R.id.director);
            txtDuration = findViewById(R.id.duration);
            txtPrice = findViewById(R.id.price);
            txtRating = findViewById(R.id.rating);
            imgPoster = findViewById(R.id.poster);
            txtGenre = findViewById(R.id.genre);
            btnBuy = findViewById(R.id.btn_buy);
            imgPoster = findViewById(R.id.poster);
            txtError = findViewById(R.id.txt_error);
            ticketView = findViewById(R.id.layout_ticket);
            progressBar = findViewById(R.id.progressBar);
     
            String barcode = getIntent().getStringExtra("code");
     
            // close the activity in case of empty barcode
            if (TextUtils.isEmpty(barcode)) {
                Toast.makeText(getApplicationContext(), "Barcode is empty!", Toast.LENGTH_LONG).show();
                finish();
            }
     
            // search the barcode
            searchBarcode(barcode);
        }
     
        /**
         * Searches the barcode by making http call
         * Request was made using Volley network library but the library is
         * not suggested in production, consider using Retrofit
         */
        private void searchBarcode(String barcode) {
            // making volley's json request
            JsonObjectRequest jsonObjReq = new JsonObjectRequest(Request.Method.GET,
                    URL + barcode, null,
                    new Response.Listener<JSONObject>() {
     
                        @Override
                        public void onResponse(JSONObject response) {
                            Log.e(TAG, "Ticket response: " + response.toString());
     
                            // check for success status
                            if (!response.has("error")) {
                                // received movie response
                                renderMovie(response);
                            } else {
                                // no movie found
                                showNoTicket();
                            }
                        }
                    }, new Response.ErrorListener() {
     
                @Override
                public void onErrorResponse(VolleyError error) {
                    Log.e(TAG, "Error: " + error.getMessage());
                    showNoTicket();
                }
            });
     
            MyApplication.getInstance().addToRequestQueue(jsonObjReq);
        }
     
        private void showNoTicket() {
            txtError.setVisibility(View.VISIBLE);
            ticketView.setVisibility(View.GONE);
            progressBar.setVisibility(View.GONE);
        }
     
        /**
         * Rendering movie details on the ticket
         */
        private void renderMovie(JSONObject response) {
            try {
     
                // converting json to movie object
                Movie movie = new Gson().fromJson(response.toString(), Movie.class);
     
                if (movie != null) {
                    txtName.setText(movie.getName());
                    txtDirector.setText(movie.getDirector());
                    txtDuration.setText(movie.getDuration());
                    txtGenre.setText(movie.getGenre());
                    txtRating.setText("" + movie.getRating());
                    txtPrice.setText(movie.getPrice());
                    Glide.with(this).load(movie.getPoster()).into(imgPoster);
     
                    if (movie.isReleased()) {
                        btnBuy.setText(getString(R.string.btn_buy_now));
                        btnBuy.setTextColor(ContextCompat.getColor(this, R.color.colorPrimary));
                    } else {
                        btnBuy.setText(getString(R.string.btn_coming_soon));
                        btnBuy.setTextColor(ContextCompat.getColor(this, R.color.btn_disabled));
                    }
                    ticketView.setVisibility(View.VISIBLE);
                    progressBar.setVisibility(View.GONE);
                } else {
                    // movie not found
                    showNoTicket();
                }
            } catch (JsonSyntaxException e) {
                Log.e(TAG, "JSON Exception: " + e.getMessage());
                showNoTicket();
                Toast.makeText(getApplicationContext(), "Error occurred. Check your LogCat for full report", Toast.LENGTH_SHORT).show();
            } catch (Exception e) {
                // exception
                showNoTicket();
                Toast.makeText(getApplicationContext(), "Error occurred. Check your LogCat for full report", Toast.LENGTH_SHORT).show();
            }
        }
     
        @Override
        public boolean onOptionsItemSelected(MenuItem item) {
            if (item.getItemId() == android.R.id.home) {
                finish();
            }
            return super.onOptionsItemSelected(item);
        }
     
        private class Movie {
            String name;
            String director;
            String poster;
            String duration;
            String genre;
            String price;
            float rating;
     
            @SerializedName("released")
            boolean isReleased;
     
            public String getName() {
                return name;
            }
     
            public String getDirector() {
                return director;
            }
     
            public String getPoster() {
                return poster;
            }
     
            public String getDuration() {
                return duration;
            }
     
            public String getGenre() {
                return genre;
            }
     
            public String getPrice() {
                return price;
            }
     
            public float getRating() {
                return rating;
            }
     
            public boolean isReleased() {
                return isReleased;
            }
        }
    }

    Sekarang jika Anda menjalankan aplikasi dan memindai kode qr yang ada dalam artikel ini, hasilnya akan ditampilkan seperti di bawah ini.


  5. Testing the AppAplikasi dapat memindai barcode apa pun namun informasi tiket film akan ditampilkan hanya bila kode qr dipindai yang disediakan dalam artikel ini.

    Jalankan aplikasi dan coba pindai kode qr di bawah ini. Anda harus bisa melihat informasi film yang tepat seperti yang ditunjukkan dalam demo.

    1. dn_barcode.jpg
    2. spiderman_barcode.jpg
    3. wonderwoman_barcode.jpg
    4. dunkirk_barcode.jpg
Sumber. https://www.androidhive.info/2017/08/android-barcode-scanner-using-google-mobile-vision-building-movie-tickets-app/

No comments