タグ別アーカイブ: android

AndroidでQRコードをリーダーを作る(超シンプル版)

GooglePlayサービス 7.8以降では「Mobile Vision API」ってのが追加されていて、これを使うとQRコードの認識が行えるようです。
ググってみると色々とサンプルはあるのですが、いかんせん複雑になっており「QRコードを認識する!」って箇所がわかり辛く思いました。
そこで超シンプルなサンプルを作成しましたよ。

簡単にQRコードが認識できると思いますよ。

まずは準備から。

build.gradleへ依存ライブラリを追記します。

dependencies {
  compile 'com.google.android.gms:play-services-vision:10.2.0'
}

次にManifestへ権限などを追記します。
下記の uses-feature、uses-permission、meta-dataを追記してください。

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.eyewhale.sample">
  
  <!-- ↓ この2行を追記  -->
  <uses-feature android:name="android.hardware.camera" android:required="true"/>
  <uses-permission android:name="android.permission.CAMERA" />
  
  <application ・・ >
    
      <!-- ↓ このmeta-dataを追記  -->
      <meta-data
          android:name="com.google.android.gms.version"
          android:value="@integer/google_play_services_version" />
      <meta-data
          android:name="com.google.android.gms.vision.DEPENDENCIES"
          android:value="barcode" />

  </application>
</manifest>

これは説明がいるか不明ですが、、超シンプルなレイアウトファイル。
SurfaceViewにカメラから撮影された画像が表示されます。
(今回のサンプルではFragmentにしてみてます。よくあるサンプルがActivityだったので)

<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">

    <SurfaceView
        android:id="@+id/camera_preview"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</FrameLayout>

ようやく実装です。
実際のコードは最後に記述しておきますが、
大きな流れはこんなかんじです。

1. SurfaceViewにコールバックを設定
2. BarcodeDetectorを作ってProcessorを設定する
3. CameraSourceを作成してBarcodeDetectorを設定
4. SurfaceViewの準備ができたらCameraSourceを開始
  →バーコード認識も開始される
5. バーコードが認識されたらProcessorのreceiveDetectionsが呼ばれるので適当に処理

あと、Pause/Resumeでカメラの停止/再開を行っています。

import com.google.android.gms.vision.CameraSource;
import com.google.android.gms.vision.Detector;
import com.google.android.gms.vision.barcode.Barcode;
import com.google.android.gms.vision.barcode.BarcodeDetector;

public class BarcodeSampleFragment extends Fragment {
  private final static String TAG = "BarcodeSampleFragment";
    
  private SurfaceView _surfaceView;
  private CameraSource _cameraSource;
  private boolean _surfaceCreated = false;
  
  @Override
  public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    View view = inflater.inflate(R.layout.fragment_barcode_sample, container, false);

    // SurfaceViewにコールバックを設定
    _surfaceView = (SurfaceView)view.findViewById(R.id.camera_preview);
    _surfaceView.getHolder().addCallback(_surfaceCallback);
    return view;
  }

  @Override
  public void onResume() {
    super.onResume();
      
    // CameraSourceが未作成 or 破棄されていたら作成
    if (_cameraSource == null) {
        setupCameraSource();
    }
        
    // SurfaceViewが準備できていたらキャプチャを開始
    if (_surfaceCreated){
        startCameraSource(_surfaceView.getHolder());
    }
  }

  @Override
  public void onPause() {
    super.onPause();
    // キャプチャを停止
    if (_cameraSource != null){
      _cameraSource.stop();
    }
  }
    
  private void setupCameraSource(){
    // QRコードを認識させるためのBarcodeDetectorを作成
    BarcodeDetector barcodeDetector = new BarcodeDetector.Builder(this.getContext())
        .setBarcodeFormats(Barcode.QR_CODE)
        .build();
    // DetectorにProcessorというコールバックを設定
    barcodeDetector.setProcessor(_detactorProcessor);

    // CameraSourceを作成
    _cameraSource = new CameraSource.Builder(this.getContext(), barcodeDetector)
        .setAutoFocusEnabled(true)
        .setFacing(CameraSource.CAMERA_FACING_BACK)
        .build();
  }

  private void startCameraSource(SurfaceHolder holder){
    try {
      _cameraSource.start(holder);
    } catch (IOException e){
        Log.e(TAG,"",e);
    }
  }
  
  private Detector.Processor<Barcode> _detactorProcessor = new Detector.Processor<Barcode>(){
    @Override
    public void release() {
    }
        
    @Override
    public void receiveDetections(Detector.Detections<Barcode> detections) {
      // 認識された結果がdetectionsに入ってる
      for(int i=0;i<detections.getDetectedItems().size();i++){
        int key = detections.getDetectedItems().keyAt(i);
        Barcode barcode = detections.getDetectedItems().get(key);
        String barcodeValue = barcode.rawValue;
        Rect rect = barcode.getBoundingBox();
        // 認識結果をログへ出力
        Log.d(TAG,"detect barcode:" + barcodeValue + " " + rect.toString());
      }
    }
  };

  private SurfaceHolder.Callback _surfaceCallback = new SurfaceHolder.Callback() {
    @Override
    public void surfaceCreated(SurfaceHolder holder) {
      try {
          // カメラのキャプチャを開始
          _cameraSource.start(holder);
      } catch (IOException e){
          Log.e(TAG,"",e);
      }
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
      _cameraSource.stop();
      _cameraSource.release();
      _cameraSource = null;
    }
  }; 
}

※ 注意!
このサンプルはシンプルにするため色々と省いてます
– カメラへアクセスするための権限チェック
– GooglePlayServicesが利用可能であるかのチェック
– BarcodeDetectorが利用可能であるかのチェック
– その他もろもろ
実際に使う時は注意してくださいね!

ギャラリーから画像を受け取る

インテントを使ってギャラリーを呼び出し、そこから画像ファイルを受け取るサンプル

呼び出す

リクエストコードを指定して ACTION_GET_CONTENT で呼び出すと
ギャラリー等のイメージを返却できるアプリを呼び出す事ができます。

private static final int REQUEST_GALLERY = 50;

Intent intent = new Intent();
intent.setType("image/*");
intent.setAction(Intent.ACTION_GET_CONTENT);
startActivityForResult(intent, REQUEST_GALLERY);

画像を受け取る

呼び出し時に指定したリクエストコードでかえってきたときに、UriからInputStreamが
取れますので、そこからBitmapを作成します。

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
  if (requestCode == REQUEST_GALLERY && resultCode == RESULT_OK) {
     Uri uri = data.getData();
     InputStream in = getContentResolver().openInputStream(uri);
     Bitmap bitmap = BitmapFactory.decodeStream(in);
     in.close();
  }
}

このサンプルではUriで渡されたデータをそのままBitmapに展開していますが、
デカい画像だとOutOfMemoryが発生すると思われますので、実際に使う場合には、
BitmapFactory.OptionsでinSampleSizeを指定するなどした方がよろしいかと。

Seekbarのつまみが消える!!

シークバーのツマミを指定したイメージに変更するサンプルを
先日書いたのですが、まれにツマミが消えてしまうみたいです。

ツマミが消えても操作はできるので画像の描画に問題があるみたい。

なんでやねん!androidまたかよ…と嘆きながら海外のサイトを漁っていると
Drawableにboundsを設定すると良いよ、みたいな記事がありました。

final Drawable d = getResources().getDrawable( R.drawable.thumb_image1); 

d.setBounds(new Rect(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight()));

slider.setThumb(d); 

なぜ、これが必要なのか。なぜ、これで直るのか。
甚だ疑問ですが、どうやら直るようです。

エイプリルフールの投稿ですが、リアルです。

アクティビティ呼び出し時に値を受け渡すサンプル

呼び出し元のActivity
UriとStringを渡しています。

Intent intent = new Intent(this,NextActivity.class);
intent.putExtra("key_filename", filename);
intent.putExtra("key_uri",  Uri.parse("xxxxxx"));
startActivity(intent);

呼び出されたActivity

Intent intent = getIntent();
Bundle bundle = intent.getExtras();
String filename = bundle.getString("key_filename ");
Uri uri = (Uri)bundle.get("key_uri");

BACKボタンが押された時に特定の処理を行う方法

androidの特徴的な機能である戻るボタン、これが押されると通常は前のactivityにもどりますが
別の処理をさせる方法です。

@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {

  if(keyCode==KeyEvent.KEYCODE_BACK){

    //ここに記述
  }
  
  return super.onKeyDown(keyCode, event);
}

戻り値にtrueを返すと、キーイベントは消費されたことになり、次のレシーバーへ通知されません。
(つまり、前のactivitiに戻りません)
falseを返すと次のレシーバーが処理を行います。

SeekBarを画像でカスタマイズ

Seekbarの背景画像とつまみを画像で変更する方法です。

下記のようなxmlをdrawableに用意して

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
  <item android:id="@android:id/background" >
    <bitmap android:src="@drawable/seekbar_image"></bitmap>
  </item>

  <item android:id="@android:id/progress" >
    <clip>
      <bitmap android:src="@drawable/seekbar_prog_image"></bitmap>
    </clip>
  </item>
</layer-list>

※ seekbar_image、seekbar_prog_imageはpngなどのdrawableリソース

このxmlをdrawableをprogressDrawableにセットすればOKです。

今回はコードでの設定例

Drawable drawable = getResources().getDrawable(R.drawable.seekbar);
seekBar.setProgressDrawable(drawable);

つまみを変更する時は下記のようにthumbに設定すればいいようです。

Drawable drawable = getResources().getDrawable(R.drawable.seekbar_thumb);
seekBar.setThumb(drawable);

Activityの履歴を消す

画面が
 A → B → C → D
と遷移して最後のDからAに戻るとします。
Aに戻るときも

Intent intent = new Intent(FinalActivity.this, MainActivity.class);
startActivity(intent);

みたいな感じですよね。きっと。

そうした時に端末の「戻る」ボタンが押された時にAからDへ戻ってしまうわけです。
これはマズい!嫌だ!

戻るボタンを利用不可にしたり、D→C→B→A と finish() をさせていったり。
面倒な事をしていたのですが、どうやら簡単に解決できるらしいです。

それがこれです

Intent intent = new Intent(FinalActivity.this, MainActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(intent);

2行目のIntent.FLAG_ACTIVITY_CLEAR_TOPを指定することで履歴(?)をクリアしてくれます。

Androidで回転するViewを非表示にするとき

回転するイメージボタンを非表示にする処理があったのですが、ちょっと躓きましたのでメモ

ボタンを回転させるにはやはりアニメーションでしょう!ってことで

RotateAnimation rotate = new RotateAnimation(0, 90, button.getWidth()/2, target.getHeight()/2);
rotate.setDuration(200);
rotate.setFillAfter(true);
button.startAnimation(rotate);

って感じで回転させてたのです。これはこれでOK

で、問題はこのボタンを非表示にする時。

button.setVisibility(View.INVISIBLE);

とかしても、消えない。消えないよぉ・・なんでだよぉ・・・
百歩譲ってアニメーション中ならわかるけど、回転終わってるじゃん。。

いろいろ頭にきますが、こんなときはアニメーションをクリアすれば消えます。

button.clearAnimation();
button.setVisibility(View.INVISIBLE);

で、さらに問題が、こんどはこれを表示するときなんですけど、当然同じ回転角で表示したいのです。

普通考えるとこんな感じ

button.setRotation(angle);
button.setVisibility(View.VISIBLE);

でもapi8(android2.2)では setRotation が使えないのです。
こまった。
で、WEBを検索するとアニメーションで時間を0にすればいいよ!との冗談みたいな方法が
至る所で書かれていたわけです。
まじですか。

しょうがないのでこんな感じで実装しました。かなしい。

RotateAnimation rotate = new RotateAnimation(0, 90, button.getWidth()/2,target.getHeight()/2);
rotate.setDuration(0);
rotate.setFillAfter(true);
button.startAnimation(rotate);

button.setVisibility(View.VISIBLE);