質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

90.11%

Androidでパターンロックを作りたい

解決済

回答 2

投稿

  • 評価
  • クリップ 0
  • VIEW 328

sena14

score 89

Androidでパターンロックのようなものを作りたいです。

・3×3のViewを配置
・なぞるとその軌跡がわかる
・一度触ったところは触れない

とりあえず正解と一致などは無視して表面的なものだけ作りたいです。

調べてみたのですがタップされたイベントの取得しかわかりませんでした。Viewがどのようになぞられたか検知する方法で参考になるものが見つからず最初から詰まってしまい困っています。

どのように実装できるのか、また参考になるものがあれば教えていただきたいです。
よろしくお願いします。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 2

checkベストアンサー

0

荒削りですが, サンプルになればと View を作ってみました.
1つの View 内でドットを 9 つ書いて, 線を引いています.
PatternLockView.OnDotTouchListener を登録すると, ドットに接触する毎にその番号が通知されます.
View を 3x3 に置いてその上に透明な View を置いた場合もこのような処理は必要になると思われますので, 3x3 に置いた View の役割が無いと思います.

package com.example.patternlock;

import android.content.Context;
import android.graphics.*;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.*;
import java.util.*;

public class PatternLockView extends View implements View.OnTouchListener {
  public PatternLockView(Context context) {
    super(context);
    initialize();
  }
  public PatternLockView(Context context, @Nullable AttributeSet attrs) {
    super(context, attrs);
    initialize();
  }
  public PatternLockView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    initialize();
  }
  public PatternLockView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
    super(context, attrs, defStyleAttr, defStyleRes);
    initialize();
  }
  private void initialize() {
    setOnTouchListener(this);
  }

  interface OnDotTouchListener {
    /**
     * index番目のドットに触れた
     * @param view 送信元View
     * @param action 発生したアクション. MotionEvent.ACTION_DOWN, MotionEvent.ACTION_MOVE, MotionEvent.ACTION_UP
     * @param index ドット位置.
     *             action=MotionEvent.ACTION_DOWN || MotionEvent.ACTION_MOVE の場合: 0[左上],1[中央上],2[右上], 3[左中央],4[中央],5[右中央], 6[左下],7[中央下],8[右下]
     *             action=MotionEvent.ACTION_UP の場合: -1
     */
    void onDotTouch(View view, int action, int index);
  }
  private OnDotTouchListener listener;
  void setOnDotTouchListener(OnDotTouchListener listener) {
    this.listener = listener;
  }

  private class Dot {
    private int SIZE = 40; //[pixel]
    final Point point; //中心位置(path用)
    final Rect rect; //描画する矩形
    Dot(int x, int y) {
      point = new Point(x, y);
      rect = new Rect(x-SIZE/2, y-SIZE/2, x+SIZE/2, y+SIZE/2);
    }
  }
  /** 各ドット */
  private ArrayList<Dot> dots;
  /** タッチ済みのドットの dots index (0~8) */
  private Set<Integer> touched = new HashSet<Integer>();

  private boolean tracking;
  private Path path;
  private Point lastDotPoint;
  private int currentTouchX;
  private int currentTouchY;

  @Override
  protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);

    if(dots == null) {
      int w = canvas.getWidth();
      int h = canvas.getHeight();

      int[] x = {h / 4, h / 2, h - h / 4};
      int[] y = {w / 4, w / 2, w - w / 4};
      dots = new ArrayList<Dot>();
      for (int i = 0; i < y.length; i++) {
        for (int j = 0; j < x.length; j++) {
          dots.add(new Dot(x[j], y[i]));
        }
      }
    }

    Paint paint = new Paint();
    paint.setAntiAlias(true);

    paint.setColor(Color.RED);
    paint.setStyle(Paint.Style.FILL);
    for (Dot dot : dots) canvas.drawRect(dot.rect, paint);

    if(path != null) {
      paint.setColor(Color.GREEN);
      paint.setStyle(Paint.Style.STROKE);
      paint.setStrokeWidth(10);
      canvas.drawPath(path, paint);
    }
    if(lastDotPoint != null) {
      paint.setColor(Color.BLUE);
      paint.setStyle(Paint.Style.STROKE);
      paint.setStrokeWidth(10);
      Path currentPath = new Path();
      currentPath.moveTo(lastDotPoint.x, lastDotPoint.y);
      currentPath.lineTo(currentTouchX, currentTouchY);
      canvas.drawPath(currentPath, paint);
    }
  }

  @Override
  public boolean onTouch(View v, MotionEvent event) {
    int action = event.getAction();
    if(action == MotionEvent.ACTION_DOWN || (tracking && action == MotionEvent.ACTION_MOVE)) {
      currentTouchX = (int)event.getX();
      currentTouchY = (int)event.getY();
      int index = searchDotIndex((int)event.getX(), (int)event.getY());
      if(index != -1 && !touched.contains(index)) { //未タッチのドット
        touched.add(index);
        if (listener != null) {
          lastDotPoint = dots.get(index).point;
          if (action == MotionEvent.ACTION_DOWN) {
            path = new Path();
            path.moveTo(dots.get(index).point.x, dots.get(index).point.y);
          } else {
            path.lineTo(dots.get(index).point.x, dots.get(index).point.y);
          }
          listener.onDotTouch(this, event.getAction(), index);
          tracking = true;
        }
      }
      invalidate();
      return true;
    } else if(tracking && action == MotionEvent.ACTION_UP) {
      if(listener != null) {
        listener.onDotTouch(this, event.getAction(), -1);
        tracking = false;
        touched.clear();
        lastDotPoint = null;
      }
      return true;
    }
    return false;
  }

  private int searchDotIndex(int x, int y) {
    for(int i=0; i<dots.size(); i++) {
      if(dots.get(i).rect.contains(x,y)) return i;
    }
    return -1; //無し
  }
}
package com.example.patternlock;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;

public class MainActivity extends AppCompatActivity {
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    PatternLockView patternLockView = findViewById(R.id.patternLockView);
    patternLockView.setOnDotTouchListener(new PatternLockView.OnDotTouchListener() {
      @Override
      public void onDotTouch(View view, int action, int index) {
        Log.d("onDotTouch","action="+action+", index="+index);
      }
    });
  }
}
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout 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">

    <com.example.patternlock.PatternLockView
        android:layout_width="300dp"
        android:layout_height="300dp"
        android:id="@+id/patternLockView"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        />

</android.support.constraint.ConstraintLayout>

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/02/14 13:38

    ありがとうございます。
    一度じっくり読ませていただきます。

    キャンセル

  • 2019/02/14 18:00

    エミュレータで試していたのですが, 実機ですとドットが小さいかもしれません.
    内部クラスの Dot にある SIZE=40 (大文字ですが final を付け忘れています 汗) を 80 や 100 等にすると, タッチし易いかもしれません.

    キャンセル

  • 2019/02/20 14:59

    ありがとうございます。
    この方針でなんとなくできそうです!

    キャンセル

0

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/02/13 22:28

    そうなんですね。座標を使わないと実装困難なんですね。
    座標を使う場合Viewが座標のどの範囲を占めているかというのはどのように判定すればいいのでしょうか?
    教えていただいたサイトではその部分がかかれていなくてそもそもドット間の判定方法が分かっていないので改造に進む方法が見えていません。
    よろしくお願いします。

    キャンセル

  • 2019/02/13 22:59

    なぞりを検出する onTouch で(onClick 等と違いまして)座標が送られてきています. また, なぞりを開始した View に対してのみ, 以降の移動や指を離したイベントが通知されるようです. それぞれ通知される座標はその View の左上を 0,0 とした座標ですので, 画面の座標に変換する等を行い View のツリーから該当する View を探す処理が必要となります.
    なぞりの対象として複数の View を用いますとこのような処理が必要になりますが, 一つの View 内であれば座標はその範囲内に収まるはずですので, 変換等が不要になります.

    キャンセル

  • 2019/02/13 23:50

    要は3×3のViewを配置したレイアウトの上に透明な画面いっぱいなレイアウトを重ねて、透明なレイアウトでOnTouchListnerを使えばいいということですよね?
    その場合下の3×3で表示しているどのViewの上を通っているのかというのは透明なレイアウトの座標からどのように算出することができるのでしょうか?

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 90.11%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる