需要のないページ

プログラミングや趣味や。

Androidで長押し可能なカスタムViewを作る

能書き

ノリでAndroid開発を始めて二日目。
それっぽい挙動をするViewを作れたので記録しておく。

f:id:LouiS:20180314175444g:plain

コードだけコピりたい人はこちら: Holdable button sample. · GitHub
なお、ド素人が書いたコードであると免責しておく*1*2

.javaファイル


    public class HoldableButton extends AppCompatButton {
        private int delayMsec;
        private int intervalMsec;
    
        private MainTask mainTask;
        private final Handler handler = new Handler();
        private Runnable runnableCode;
    
        //
        //
        public HoldableButton(Context context) {
            this(context, null);
        }
        public HoldableButton(Context context, AttributeSet attrs) {
            this(context, attrs, 0);
        }
        public HoldableButton(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
            initAttrs(
                context.obtainStyledAttributes(
                    attrs, R.styleable.HoldableButton, defStyleAttr, 0
                )
            );
            setMainTask(() -> {});
        }
        private void initAttrs(TypedArray typedArray) {
            delayMsec = typedArray.getInteger(
                R.styleable.HoldableButton_delay_msec, 400
            );
            intervalMsec = typedArray.getInteger(
                R.styleable.HoldableButton_interval_msec, 100
            );
            typedArray.recycle();
        }
        private void initListener() {
            setOnClickListener(view -> mainTask.doTask());
    
            //
            runnableCode = () -> {
                mainTask.doTask();
                handler.postDelayed(runnableCode, intervalMsec);
            };
            setOnTouchListener(
                (view, motionEvent) -> {
                    switch(motionEvent.getAction()) {
                    case MotionEvent.ACTION_DOWN:
                        handler.postDelayed(runnableCode, delayMsec);
                        break;
    
                    case MotionEvent.ACTION_UP:
                        handler.removeCallbacks(runnableCode);
                        view.performClick();
                        break;
    
                    default:
                        break;
                    }
    
                    return true;
                }
            );
        }
    
        //
        public void setMainTask(MainTask mainTask) {
            this.mainTask = mainTask;
            initListener();
        }
    
        //
        //
        @FunctionalInterface
        public interface MainTask {
            void doTask();
        }
    }

    

果たすべきメインタスクを文字通りmainTaskとして保持している。

  • タップが短いとき → mainTask.doTaskを一度だけ呼び出す。
  • タップが長いとき → mainTask.doTaskをintervalMsecおきに呼び出す。

そしてタップが『長い』と判断する閾値がdelayMsecなのである。
これらのパラメータを扱いやすくするため、属性とすることにした。

.xmlファイル


    <?xml version="1.0" encoding="utf-8"?>
    <resources>
        <declare-styleable name="HoldableButton">
            <attr name="delay_msec" format="integer" />
            <attr name="interval_msec" format="integer" />
        </declare-styleable>
    </resources>
    

正直今の段階では『なんとなく感』が強いので解説は避ける。

また、このままだとボタンが潰れて表示される
三時間粘った末、解決策が発見できた。

You should explicitly set android:theme. That theme should be derived from Widget.AppCompat.Button theme.

先人は偉大だ。

むすび

試行錯誤しながら、主に次のリンク先の情報を元に完成させることが出来た。

12436288584_94d6bc46d2_b.jpg
背景 View上で長押しを判定したい時、通常はGestureDetectorクラスのOnGestureListenerをimplementsしてonLongPressを実装する。 [中略] しかしonLongPressは長押し判定の時間を指定できない。

Runnable#runメソッドを再帰的に呼び出す方法は目から鱗であった。

*1:参考書も買わず、ググりながら適当に書いている。さすがにコピペはしてないけど。

*2:実際、絶対もっと簡単に書けるよね。こういうUIよく見るもん。

/* コードブロック */