2011年02月27日

やっぱり駄目だぁ!(EditTextのスクロール)

先日EditTextにScrollViewを組み込んで実用的なスクロール速度になったというエントリーを書いて、いい気になっていた私ですが、テストしていたら大問題が発覚して再び失意のズンドコ、じゃなかったどん底に陥りました。
というのは、スクロールしていって、どこかでタップすると、EditTextの読み込んでいる文書の先頭行にカーソルが戻ってしまうのです。
最初はどこかで記述ミスでもしているのかと思ったのですが、どうもそうじゃないらしい。少なくとも記述ミスは見つけられないのです。一昨日からずっとこの問題が気になって気になって、ずっとやっているのですが、解決できません。
このスクロール速度は(ScrollViewを使わない場合が異常に遅いため)捨てがたいので、どうしても解決したいのですが、どうにもうまくいきません。
とはいえ、ある程度までは改善する方法はあるのですが、これにも深刻な問題があります。
その方法は

editText.setOnTouchListener(new View.OnTouchListener() {
public boolean onTouch(View v, MotionEvent event) {
editText.scrollTo(0,scrollView.getScrollY());
editText.moveCursorToVisibleOffset();
return false;
}
});

として、editTextをScrollViewのスクロールしたところまでスクロールさせる方法。
実際はタップした場所とカーソルが移る場所がなぜか微妙にずれているように見えますが、タップと同時にソフトキーボードが出てしまうので、はっきり分からないながらも近い位置にカーソルが移動します。
これがeditText.setSelection(etBody.getSelectionStart());とかではNGなので、現状としてはもっとも解決に近いように思えますが、スクロールを続けて読み込んでいる文書の末尾を過ぎてもスクロールし、画面が真っ白になってしまいます。
if(scrollView.getScrollY() < editText.getBottom()){}などと制限をかけてみても変わらず。

なので、実際には使い物になりません。

絶対にどこか間違いをしているのではないかと思いますが、なぜこんな単純なことが単純にできないのかも疑問に思います。
しかもWebで情報を探しても例えば「Andorid EditText スクロール」とかで検索しても驚くほど情報は少ないのです。
同じようなことをしている人はたくさんいそうなものなのに不思議です。
エディタ系のアプリを作成している人はどうしているのだろうかとも思います。
ソースを見せて欲しい。(笑)

いずれにしてもこの問題が解決しないと公開できない気がしています。
もう三月になるというのに、いつになったら完成するものやら。
しかも、完成が遅れまくったことによりシーズン到来!(花粉の)
そのため、集中力が20%以下になり、ただでさえ他の人より集中力がないので、常人の100分の1くらいの集中力で作業をしなければいけないのではないでしょうか?

ああ、もうアプリ公開などということは、僕にとっては永遠に「到達不能コード」なのでしょうか?
posted by 白虹 at 22:15| Comment(1) | TrackBack(0) | Android開発
この記事へのコメント
私も同じ問題に遭遇して、こちらのブログにたどり着きました。
EditTextをタップすると先頭にカーソルが移動してしまうのは、たぶんバグではなく仕様だと思います。
実際、わたしの使っている携帯の純正Eメールアプリも、同じ動作をしますので。
でもやっぱりどうにかしたいので、この記事に書いていただいているコードも参考にしながら試行錯誤しました。

タップすると、カーソルはいったんタップした場所に移動した後、先頭に移動するようです。
ということは、移動が完全に終了してから、強制的にタップした場所に戻してやればいいはずです。
カーソルが移動した場所を取得するには、onSelectionChangedをオーバーライドすればOKです。
問題は移動が完全に終了したことをどのように検知するかなのですが、onMeasureをオーバーライドするといいようです。

以下のコードで、見事成功しました。
もう3年も経っていますので、管理人様のお役に立てるかどうかはわかりませんが、私と同じようにこのブログにたどり着く人もいるかもしれませんので、コメントに残しておきます。



***** ScrollView をカスタマイズ *****

class CustomScrollView extends ScrollView {

 public CustomScrollView(Context context) {
  super(context);
 }

 // 自前の OnMeasureListener を実装する

 static interface OnMeasureListener {
  void onMeasure(int height);
 }

 private OnMeasureListener listenerOnMeasure;

 void setOnMeasureListener(OnMeasureListener listenerOnMeasure) {
  this.listenerOnMeasure = listenerOnMeasure;
 }

 @Override
 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  if ( listenerOnMeasure != null ) {
   listenerOnMeasure.onMeasure( MeasureSpec.getSize(heightMeasureSpec) );
  }
  super.onMeasure(widthMeasureSpec, heightMeasureSpec);
 }

}

***** EditText をカスタマイズ *****

class CustomEditText extends EditText {

 public CustomEditText(Context context) {
  super(context);
 }

 // 自前の OnSelectionChangedListener を実装する

 static interface OnSelectionChangedListener {
  void onSelectionChanged(int selStart, int selEnd);
 }

 private OnSelectionChangedListener listenerOnSelectionChanged;

 void setOnSelectionChangedListener(OnSelectionChangedListener listenerOnSelectionChanged) {
  this.listenerOnSelectionChanged = listenerOnSelectionChanged;
 }

 @Override
 protected void onSelectionChanged(int selStart, int selEnd) {
  super.onSelectionChanged(selStart, selEnd);
   if ( listenerOnSelectionChanged != null ) {
    listenerOnSelectionChanged.onSelectionChanged(selStart, selEnd);
   }
  }
 }

}

***** 上記2つを使用する側のコード *****

 void 初期化() {
  editText.setOnTouchListener(
   new View.OnTouchListener() {
    public boolean onTouch(View view, MotionEvent event) {
     onEditTextTouch();
     return false;
    }
   }
  );
  editText.setOnSelectionChangedListener(
   new CustomEditText.OnSelectionChangedListener() {
    public void onSelectionChanged(int selStart, int selEnd) {
     onEditTextSelectionChanged(selStart, selEnd);
    }
   }
  );
  scrollEditText.setOnMeasureListener(
   new CustomScrollView.OnMeasureListener() {
    public void onMeasure(int height) {
     onEditTextMeasure(height);
      // EditText は見えない範囲を含む高さであり、
      // ScrollView は見える範囲のみの高さである
      // このメソッドでは ScrollView のほうを利用して、見える範囲のみの高さを取得していることに注意
     }
    }
   );
  }

  private int prevHeight = 0;
  private int firstSelStart = -1;
  private int firstSelEnd = -1;

  private void onEditTextTouch() {
   firstSelStart = -1;
   firstSelEnd = -1;
  }

  private void onEditTextSelectionChanged(int selStart, int selEnd) {
   if ( firstSelStart < 0 ) {
    firstSelStart = selStart;
    firstSelEnd = selEnd;
   }
  }

  private void onEditTextMeasure(int height) {

   if ( height < prevHeight ) {
    // 前回の高さよりも減少しているならば、ソフトキーボードが出現し、選択範囲の移動が完了していることになる
    // その過程で、以下のイベントが発生しているはず
    // onEditTextTouch() → onEditTextSelectionChanged()正しい値 → onEditTextSelectionChanged()誤った値
    // そこで、onEditTextTouch()発生後の最初の選択範囲を記憶しておき、強制的にその範囲に戻す
    if ( firstSelStart >= 0 ) {
     editText.setSelection(firstSelStart, firstSelEnd);
    }
   }
   prevHeight = height;
   firstSelStart = -1;
   firstSelEnd = -1;

  }
Posted by 通りすがりのアマチュアプログラマ at 2014年02月11日 19:03
コメントを書く
お名前:

メールアドレス:

ホームページアドレス:

コメント:

この記事へのトラックバックURL
http://blog.sakura.ne.jp/tb/43611908

この記事へのトラックバック