Windows Phone 7 の ListBox コントロールはタッチスクロールに精密に反応しない

Windows Phone 7 の ListBox コントロールは、コンテンツが重いときに、スクロール操作への反応が精密でなくなるというバグがあるように思う。January Update, March Update (NoDo) のどちらでも再現する。

以下、再現手順。リストビューをスクロール開始(タッチダウン)したときと、フリック開始(タッチアップ)したときに、VSのコンソールにリストビューのオフセットを表示するコードになる。

  1. VSでWindowsPhoneDataBoundApplicationを作る
  2. ViewModels.MainViewModel:LoadDataを修正してデータ量を十倍にする(forで繰り返す)
  3. MainPage.xaml.cs の MainPage クラスの最後に次のコードを書く
    1. private static readonly Brush RedBrush = new SolidColorBrush(Colors.Red);
      private static readonly Brush WhiteBrush = new SolidColorBrush(Colors.White);
      private ScrollViewer scrollviewer;

      private void MainListBox_ManipulationStarted(object sender, ManipulationStartedEventArgs e) {
          this.PageTitle.Foreground = RedBrush;
          if(scrollviewer == null) {
              scrollviewer = FindChild<ScrollViewer>(this.MainListBox);
          }
          var value = scrollviewer.VerticalOffset;
          Debug.WriteLine("start: {0}", value);
          var timer = new DispatcherTimer();
          timer.Interval = TimeSpan.FromSeconds(1);
          timer.Tick += delegate {
              timer.Stop();
              var v = scrollviewer.VerticalOffset;
              Debug.WriteLine("reset: {0}", v);
              scrollviewer.ScrollToVerticalOffset(v);
          };
          //===========
          // timer.Start();
          //===========
      }

      private void MainListBox_ManipulationCompleted(object sender, ManipulationCompletedEventArgs e) {
          this.PageTitle.Foreground = WhiteBrush;
          var value = scrollviewer.VerticalOffset;
          Debug.WriteLine("end  : {0}", value);
      }

      private static T FindChild<T>(DependencyObject o) where T : DependencyObject {
          if(o == null) {
              return default(T);
          }
          else {
              var childrenCount = VisualTreeHelper.GetChildrenCount(o);
              for(var i = 0; i < childrenCount; i++) {
                  var child = VisualTreeHelper.GetChild(o, i);
                  if(child is T) {
                      return (T)child;
                  }
                  else {
                      var foundChild = FindChild<T>(child);
                      if(foundChild != null) {
                          return foundChild;
                      }
                  }
              }
              return default(T);
          }
      }

  4. MainPage.xaml の MainListBox 要素の属性を追加する。
    ManipulationStarted="MainListBox_ManipulationStarted" ManipulationCompleted="MainListBox_ManipulationCompleted"

これでデバッグビルドで実機でテストする。フリックでスクロールさせながら、タッチダウンして止めたときのオフセットと、そのままタッチアップしたときのオフセットをくべる。すると、タッチダウンからタッチアップまでに、多いときは3程度までオフセットが変化しているときがある。この数値はアイテムインデックスなので、つまり、タッチダウンしてから3項目分もスクロールして、ようやく止まっているということになる。はっきり言って、使い物にならないレベルの違和感がある。

これに対策するために、タッチダウン時に強制的にスクロールアニメーションを停止してみることを試した。コード中の、”//=====” で囲んだ部分のコメントを外すと、タッチダウンから一秒後に、VerticalOffset の値まで ScrollTo するという動作になる。

これは、直感的には、一秒後にスクロールアニメーションがピタッと止まるようになると思われる。しかし、実際には必ず位置が「バックステップ」する。なぜこのようになるのか分からないし、”SetOffset” ではなく “ScrollTo” であることから、これが正しい動作なのかもしれないが、まずはさっさと普通の動作が行えるようになってほしいというのが本音である。

 

関連情報

How to detect when a list is scrolling (or not)

http://blogs.msdn.com/b/ptorr/archive/2010/07/23/how-to-detect-when-a-list-is-scrolling-or-not.aspx