HorizontalScrollView への矢印アイコンの表示

米国アマゾンでは Amazon Appstore for Android というのがあって、 Android 用のアプリケーションが買えるようになっているのですが、 その画面についているメニューがとてもわかりやすくて気に入りました。

横方向へのスクロールするメニューなのですが、左側へのスクロール、右側へのスクロールの有無によって、左または右への矢印が表示されます。

iPhoneやAndroidの画面はタッチスクリーンが基本で、指でスクロールする動作はたとえ矢印がなくてもユーザーは迷わないのかもしれませんが、 やっぱりあった方がわかりやすいんじゃないかなぁ、と思ったのでした。

そこでどうすれば実装できるか調べてみたのですが、なかなか一発で簡単に矢印が出てくるようなものでもないようです。

十分に調べ切れているか分からないので、良い方法かどうか分かりませんが、だいたい似たようなスクロールビューができたのでその実装方法をここに示しておきます。

Amazon Appstore for Android の画面

アマゾンアップストアのホーム画面は次のようになっています。

Amazon AppStore のスクリーンショット

このスクリーンショットでは、一際目を引くのは アングリーバードですが・・・ (笑)、 ここで私が着目したいのはメニューの部分です。拡大してみましょう。

Amazon AppStore のスクリーンショット

スクロールされる内容の左端が表示されているときは、右側へスクロール可能であることを意味する右向き矢印が表示されます。また、画面をよく見るとスクロールされるメニューの上下にはボーダーが表示されています。

上のスクリーンショットをよくみると、影がついていたり、スクロールする中身の端がグラデーションで消えていたりと少し複雑ですが、ここでは単純化して、左右矢印の表示とボーダーの表示をゴールに設定します。

ここで実装した矢印付きスクロールバー

先にここで実装するスクロールバーの出来上がりをみてみましょう。

初期状態では右側へスクロール可能なので、右向き矢印が表示されています。

HorizontalScrollView への矢印表示

途中までスクロールすると、左右両方へスクロール可能な状態になるのでスクロールバーの両側に矢印が表示されています。

HorizontalScrollView への矢印表示

右端までスクロールするとこれ以上右へはスクロールできません。右向きの矢印を非表示として、左向きの矢印だけが表示されています。

HorizontalScrollView への矢印表示

ボーダー・矢印表示付き HorizontalScrollView の実装

水平方向のスクロールなので、 HorizontalScrollView を派生しましょう。

基本戦略は単純です。派生クラスの onDraw メソッドでボーダーを描画します。また、必要に応じて左または右向きの矢印画像を表示します。

HorizontalScrollView クラスの派生クラスの作成

水平方向にスクロールするビューといえば、 HorizontalScrollView があります。これを派生して、「ボーダー付き」「矢印アイコン表示」という二つの機能を追加します。

骨組みは次のようになります。 コンストラクタを三つオーバーライド。さらに onDraw でボーダーを描画するとか、アイコンを表示するということをします。

import android.content.Context;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.widget.HorizontalScrollView;

public class MyHorizontalScrollView 
    extends HorizontalScrollView {
  
  public MyHorizontalScrollView(Context context, 
    AttributeSet attrs,int defStyle) {
    super(context, attrs, defStyle);
  }

  public MyHorizontalScrollView(Context context, 
    AttributeSet attrs) {
    super(context, attrs);
  }

  public MyHorizontalScrollView(Context context) {
    super(context);
  }
  
  @Override
  public void onDraw(Canvas canvas){
    super.onDraw(canvas);
    ...    
  }
}

これを利用するのは簡単です。 layout/main.xml で次のようにしてクラス名を指定すれば OK です。

<com.keicode.android.test.MyHorizontalScrollView
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:id="@+id/horizontal_scrollview"

画像の表示

左右それぞれの方向を向く矢印表示のため、次のような画像を用意します。

left_arrow.pngright_arrow.png

これらを res/drawable フォルダに保存します。するとコードからはその ID は R.drawable.left_arrow とか R.drawable.right_arrow で取得できます。

onDraw では Canvas オブジェクトを取得出きるので、それを利用して画像を表示します。

Canvas でのビットマップ表示は次のようなコードです。

Bitmap bitmap = BitmapFactory.decodeResource(
  this.getResources(), R.drawable.right_arrow);
canvas.drawBitmap(bitmap, x, y, null);      

x, y はその名の通り x, y 座標ですが、これについては後述します。

ボーダーの描画

ボーダーラインは、単純に直線の描画です。

Canvas での直線の描画は drawLine メソッドで次のようにします。

canvas.drawLine(x1, y1, x2, y2, paint);

とても単純ですね。色の指定は paint オブジェクトの属性として設定します。

x, y の指定についてはこちらもまた後述します。

HorizontalScrollView の onDraw での座標

ボーダーの描画および画像の表示をするために、それぞれのサイズや座標を知る必要があります。

HorizontalScrollView

コンテンツを含む HorizontalScrollView の全体のサイズ、及びその可視領域は上の図のメソッドで取得できます。

View 自体のサイズは高さ、幅はそれぞれ getHeight()、computeHorizontalScrollRange()メソッドで取得できます。 可視領域の幅は getWidth()、また View全体のサイズに対する可視領域の横方向のオフセットは、 computeHorizontalScrollOffset() で取得できます。

したがって、ビューの可視部分の上側と下側にボーダー線を描画するなら、その x 座標は offset から offset + width となります。 y座標は 0 から height にボーダー自身の太さ(幅)の半分のオフセットを考慮したものです。

矢印アイコンの配置場所は可視領域の左右になりますから、左側のアイコンは (offset,0) に配置、 右側のアイコンは (offset + width - (アイコンの幅), 0) になります。

スクロールバーを消す

横方向のスクロールバーを消します。

これには、次のように setHorizontalScrollBarEnabled で非表示にする方法がひとつ。

horizontalScrollView.setHorizontalScrollBarEnabled(false);

そしてもう一つの方法は、 xml の定義で android:scrollbars="none" とすることです。

エッジのフェイディングを無効にする

HorizontalScrollView はデフォルトで左右がフェイドアウトするようになっています。このままだと、今回配置した画像やボーダーがつながらずおかしなことになってしまいます。 下の図のように途切れてしまいます。

フェイディングエッジ

これを防ぐには、 android:fadingEdge="none" を指定することによってフェイディングを無効化します。

コードの全体

以上を踏まえると、矢印アイコンとボーダー付きスクロールビューは次のようになります。

package com.keicode.android.test;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.widget.HorizontalScrollView;

public class MyHorizontalScrollView 
  extends HorizontalScrollView {

  protected final int borderWidth = 4;
  
  public MyHorizontalScrollView(
    Context context, AttributeSet attrs,int defStyle) {
    super(context, attrs, defStyle);
  }


  public MyHorizontalScrollView(Context context, 
    AttributeSet attrs) {
    super(context, attrs);
  }

  public MyHorizontalScrollView(Context context) {
    super(context);
  }
  
  @Override
  public void onDraw(Canvas canvas){
    
    super.onDraw(canvas);
    
    int offset = this.computeHorizontalScrollOffset();
    int scrollRange = this.computeHorizontalScrollRange();
        
    int width = this.getWidth();
    int height = this.getHeight();
    int toend = scrollRange - (width+offset);

    Paint paint = new Paint();
    paint.setColor(getResources().getColor(R.color.lightgray));
    paint.setStrokeWidth(borderWidth);
    
    //
    // Draw Borders
    //
    
    // Top
    canvas.drawLine(
      offset, borderWidth/2, 
      width+offset, borderWidth/2, 
      paint);
    // Bottom
    canvas.drawLine(
      offset, height-borderWidth/2, 
      width+offset, height-borderWidth/2, 
      paint);

    //
    // Draw Arrows
    //
    
    // Left Arrow
    if(40 < offset){
      Bitmap larrowBitmap = BitmapFactory.decodeResource(
        this.getResources(), R.drawable.left_arrow);
      int bitmapPos = offset;

      canvas.drawBitmap(larrowBitmap, bitmapPos, 0, null);
    }
    
    // Right Arrow
    if(40 < toend){
      Bitmap rarrowBitmap = BitmapFactory.decodeResource(
        this.getResources(), R.drawable.right_arrow);
      int bitmapPos = offset + width - rarrowBitmap.getWidth();

      canvas.drawBitmap(rarrowBitmap, bitmapPos, 0, null);    
    }  
  }
}

適当に値を決め打ちしているところもあるので、実際に利用する歳には適当に直してください。

ここまでお読みいただき、誠にありがとうございます。SNS 等でこの記事をシェアしていただけますと、大変励みになります。どうぞよろしくお願いします。

© 2024 Android 開発入門