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

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

ただいまの
回答率

90.86%

  • Android

    6093questions

    Androidは、Google社が開発したスマートフォンやタブレットなど携帯端末向けのプラットフォームです。 カーネル・ミドルウェア・ユーザーインターフェイス・ウェブブラウザ・電話帳などのアプリケーションやソフトウェアをひとつにまとめて構成。 カーネル・ライブラリ・ランタイムはほとんどがC言語/C++、アプリケーションなどはJavaSEのサブセットとAndroid環境で書かれています。

  • Xamarin

    438questions

    Xamarin(ザマリン)は、iPhoneなどのiOSやAndroidで動作し、C# 言語を用いてアプリを開発できるクロスプラットフォーム開発環境です。Xamarin Studioと C# 言語を用いて、 iOS と Android の両方の開発を行うことができます。

Xamarin.Android ListView 画像つき 高速処理

解決済

回答 1

投稿 編集

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

dfrd

score 4

Xamarin.Android
ListViewで、サムネ付きのリストアイテムを表示しています。
サムネと、テキストは、URLから、取得していて、無事に表示は出来ているのですが、

スクロールをすると、重くて、カクカクしながら表示される感じになります。

何か良い方法はありませんでしょうか?

処理の順番としては次の順番になります。

(1)APIで、記事データを取得 : AsyncTaskで処理

(2)List<T>にデータを格納

(3)Adapterにリストデータをセット

(4)AdapterClass内で、
GetView関数が処理開始。

(5)GetViewメソッドで、テキスト、画像をセットする。

まだまだ、初心者で申し訳ありませんが、ご教授をお願い致します。

以下に、Adapterのソースコードを記載させて頂きます。

namespace SampleArticleMemberApp.Droid
{
    public class ArticleListItemAdapter : BaseAdapter<ArticleListItem>
    {
        public List<ArticleListItem> items;
        public Activity context;

        /**
         * コンストラクタ
         */
        public ArticleListItemAdapter(Activity context, List<ArticleListItem> items)
        {
            Debug.Log(Debug.DEBUG_LEVEL_VERBOSE, "ArticleListItemAdapter", "-----START-----");
            this.context = context;
            this.items = items;
            Debug.Log(Debug.DEBUG_LEVEL_VERBOSE, "ArticleListItemAdapter", "-----END-----");
        }

        /**
         * クリック処理
         */
        public override long GetItemId(int position)
        {
            Debug.Log(Debug.DEBUG_LEVEL_VERBOSE, "GetItemId", "-----START-----");
            Debug.Log(Debug.DEBUG_LEVEL_VERBOSE, "GetItemId", "-----END-----");
            return position;
        }

        public override ArticleListItem this[int position]
        {
            get { return items[position]; }
        }

        public override int Count
        {
            get { return items.Count; }
        }

        public override View GetView(int position, View convertView, ViewGroup parent)
        {
            Debug.Log(Debug.DEBUG_LEVEL_VERBOSE, "GetView", "-----START-----");
            var item = items[position];

            View view = convertView;
            if (view == null){
                view = context.LayoutInflater.Inflate(Resource.Layout.ArticleListViewItem, null);
            }

            // BaseAdapter<T>の対応するプロパティを割り当て
            view.FindViewById<TextView>(Resource.Id.articleItemDate).Text = item.date;
            view.FindViewById<TextView>(Resource.Id.articleItemTitle).Text = item.title;

            Debug.Log(Debug.DEBUG_LEVEL_INFO, "GetView", item.title);
            Debug.Log(Debug.DEBUG_LEVEL_INFO, "GetView", item.url);
            Debug.Log(Debug.DEBUG_LEVEL_INFO, "GetView", item.thumbUrl);
            Debug.Log(Debug.DEBUG_LEVEL_INFO, "GetView", "readable: " +item.readable.ToString());

            if(item.thumbUrl != ""){
                Debug.Log(Debug.DEBUG_LEVEL_INFO, "GetView", "画像を読み込みます");
                view.FindViewById<ImageView>(Resource.Id.articleItemThumb).SetImageResource(Resource.Mipmap.def_thumb);
                SetThumb(view, position);
                //var image = GetImageBitmapFromUrl(item.thumbUrl);
                //view.FindViewById<ImageView>(Resource.Id.articleItemThumb).SetImageBitmap(image);
            }
            else{
                view.FindViewById<ImageView>(Resource.Id.articleItemThumb).SetImageResource(Resource.Mipmap.def_thumb);
            }

            //読み込み不可能ならば、背景色を変える
            if(!item.readable){
                view.SetBackgroundColor(Color.DarkGray);
                view.FindViewById<TextView>(Resource.Id.articleItemDate).SetTextColor(Color.GhostWhite);
                view.FindViewById<TextView>(Resource.Id.articleItemDate).Alpha = 0.4f;

                view.FindViewById<TextView>(Resource.Id.articleItemTitle).SetTextColor(Color.GhostWhite);
                view.FindViewById<TextView>(Resource.Id.articleItemTitle).Alpha = 0.4f;

                view.FindViewById<ImageView>(Resource.Id.articleItemThumb).Alpha = 0.4f;
            }
            else{
                view.SetBackgroundColor(Color.GhostWhite);
                view.FindViewById<TextView>(Resource.Id.articleItemDate).SetTextColor(Color.Black);
                view.FindViewById<TextView>(Resource.Id.articleItemDate).Alpha = 1.0f;

                view.FindViewById<TextView>(Resource.Id.articleItemTitle).SetTextColor(Color.Black);
                view.FindViewById<TextView>(Resource.Id.articleItemTitle).Alpha = 1.0f;

                view.FindViewById<ImageView>(Resource.Id.articleItemThumb).Alpha = 1.0f;
            }


            Debug.Log(Debug.DEBUG_LEVEL_VERBOSE, "GetView", "-----END-----");
            return view;
        }

        /**
         * URLから、Bitmap生成
         */
        private async Task<Bitmap> GetImageBitmapFromUrl(string url)
        {
            Bitmap imageBitmap = null;

            try{
                using (var webClient = new WebClient())
                {
                    var imageBytes = webClient.DownloadData(url);
                    if (imageBytes != null && imageBytes.Length > 0)
                    {
                        imageBitmap = BitmapFactory.DecodeByteArray(imageBytes, 0, imageBytes.Length);
                    }
                }

            }
            catch(Exception e)
            {
                Debug.Log(Debug.DEBUG_LEVEL_ERROR, "GetImageBitmapFromUrl", "e: " +e);
            }
            return imageBitmap;
        }

        private async void SetThumb(View view, int position)
        {
            var item = items[position];
            if(items[position].bitmap  == null)
            {
                ImageView imageView = view.FindViewById<ImageView>(Resource.Id.articleItemThumb);

                Debug.Log(Debug.DEBUG_LEVEL_ERROR, "GetImageBitmapFromUrl", "imgvW: " + imageView.Width);
                Debug.Log(Debug.DEBUG_LEVEL_ERROR, "GetImageBitmapFromUrl", "imgvH: " + imageView.Height);
                if(0 < imageView.Width && 0 < imageView.Height){
                    Bitmap bmp = await GetImageBitmapFromUrl(item.thumbUrl);
                    int bmpH = (int)((float)imageView.Width / (float)bmp.Width *(float)bmp.Height);

                    int scale = Math.Max(imageView.Width, bmpH);

                    bmp = Bitmap.CreateScaledBitmap(bmp, imageView.Width, bmpH, false);
                    items[position].bitmap = await GetImageBitmapFromUrl(item.thumbUrl);
                    view.FindViewById<ImageView>(Resource.Id.articleItemThumb).SetImageBitmap(items[position].bitmap);
                    Debug.Log(Debug.DEBUG_LEVEL_ERROR, "GetImageBitmapFromUrl", "bmpW: " + bmp.Width);
                    Debug.Log(Debug.DEBUG_LEVEL_ERROR, "GetImageBitmapFromUrl", "bmpH: " + bmp.Height);

                    bmp.Recycle();
                    bmp = null;
                }
            }
            else
            {
                view.FindViewById<ImageView>(Resource.Id.articleItemThumb).SetImageBitmap(items[position].bitmap);
            }
        }
    }
}

以上です。
よろしくお願い致します。

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

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

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

    クリップを取り消します

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

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

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

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

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

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

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

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

    質問の評価を下げる

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

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

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

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

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

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

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

    詳細な説明はこちら

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

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

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

質問への追記・修正、ベストアンサー選択の依頼

  • f-miyu

    2018/07/07 08:56

    実際のソースコード(特にGetViewメソッド)を載せていただけると、ここが原因だとアドバイスできるかもしれません。

    キャンセル

  • dfrd

    2018/07/07 23:23

    全体的にクラスのソースを記載させて頂きました。お恥ずかしいソースですが、よろしくお願い致します。

    キャンセル

回答 1

checkベストアンサー

+1

GetImageBitmapFromUrlwebClient.DownloadData(url)で画像が取得できるまで処理を止めてしまうので、カクカクした動きになってしまっています。
ここを非同期処理にすれば改善されるかと思います。

private async Task<Bitmap> GetImageBitmapFromUrl(string url)
{
    Bitmap imageBitmap = null;

    try
    {
        using (var webClient = new WebClient())
        {
            var tcs = new TaskCompletionSource<Bitmap>();

            webClient.DownloadDataCompleted += (sender, e) =>
            {
                var imageBytes = e.Result;
                if (imageBytes != null && imageBytes.Length > 0)
                {
                    imageBitmap = BitmapFactory.DecodeByteArray(imageBytes, 0, imageBytes.Length);
                    tcs.SetResult(imageBitmap);
                }
            };

            webClient.DownloadDataAsync(new Uri(url));

            imageBitmap = await tcs.Task;
        }
    }
    catch (Exception e)
    {
        Debug.Log(Debug.DEBUG_LEVEL_ERROR, "GetImageBitmapFromUrl", "e: " +e);
    }
    return imageBitmap;
}

試したところ、これで一応改善はされましたが、より効率化を目指すのであれば、画像の非同期取得やキャッシュを行うための優れたOSSライブラリがあるので、それらの使用を検討されてはいかがでしょうか?

Xamarin.Androidに対応したライブラリとしては下記のものがあります。

GlideやPicassoは、元のJavaでもよく使われているライブラリです。
例としてFFImageLoadingを使えば、こんな感じで簡単に画像の取得が出来ます。

ImageService.Instance.LoadUrl(url).Into(imageView):

 追記

今のコードのままでは、少し問題が出てくるかもしれません。

  1. 画像取得に失敗(通信できないなど)したら、Taskが終わらない
  2. スクロールしなければ、画像の読み込みが行われない

1.に関しては、DownloadDataCompletedを以下のように修正すれば、大丈夫かと思います。

webClient.DownloadDataCompleted += (sender, e) =>
{
    Bitmap image = null;
    try
    {
        if (!e.Cancelled && e.Error == null)
        {
            var imageBytes = e.Result;
            if (imageBytes != null && imageBytes.Length > 0)
            {
                image = BitmapFactory.DecodeByteArray(imageBytes, 0, imageBytes.Length);
            }
        }
    }
    finally
    {
        tcs.SetResult(image);
    }
};


ただ、GetImageBitmapFromUrlnullを返すので、このメソッドを使っているSetThumbでは、nullチェックが必要になります。

2.に関しては、SetThumbで、imageViewのサイズチェックをして、0なら処理を行なっていないためです。この時imageViewはまだサイズが確定していないので、WidthHeightは0を返します。
よくよく見ると、リサイズ処理は行っているようですが、使ってはいないようなので、サイズチェック自体を行わないようにするのが一番簡単かと思います。

先に述べたライブラリを使えば、リサイズも簡単に行ってくれるので、できるなら使用することを強くお勧めします。

投稿

編集

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2018/07/08 08:33

    f-miyu さん

    ありがとうございます。
    AsyncTaskをとりあえず入れてみれば、非同期になり、大丈夫かと思ったのですが、まだもう少し足りなかったのですね。
    一旦、これでやってみようかと思います♪
    やってみたあと、報告させて頂きます。

    そして、そのあとに、
    (まだ始めたばかりなので、)OSSライブラリなどは、使ったことがなく、ここで再度、聞くかもしれません。

    その時は、お手数ごございますが、またよろしくお願い致します。

    キャンセル

  • 2018/07/08 11:14

    無事に、カクカクは無くなりました。
    ありがとうございます。

    イメージキャッシュ、ローディングなどのOSSは、一旦リリースを先にするため、あとで設定したいと思います。

    もしかしたら、別スレッドで立ち上げるかもしれません。
    その時は、お手数でございますが、もしよろしければ、教えてもらえますと幸いです。

    よろしくお願い致します。

    キャンセル

  • 2018/07/08 13:52

    少しきになることがあったので、回答に追記しました。

    キャンセル

  • 2018/07/09 09:11

    ありがとうございます!
    ここまで、丁寧に。。。
    とても助かります。

    まだまだ、初心者なもので。

    キャンセル

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

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

関連した質問

同じタグがついた質問を見る

  • Android

    6093questions

    Androidは、Google社が開発したスマートフォンやタブレットなど携帯端末向けのプラットフォームです。 カーネル・ミドルウェア・ユーザーインターフェイス・ウェブブラウザ・電話帳などのアプリケーションやソフトウェアをひとつにまとめて構成。 カーネル・ライブラリ・ランタイムはほとんどがC言語/C++、アプリケーションなどはJavaSEのサブセットとAndroid環境で書かれています。

  • Xamarin

    438questions

    Xamarin(ザマリン)は、iPhoneなどのiOSやAndroidで動作し、C# 言語を用いてアプリを開発できるクロスプラットフォーム開発環境です。Xamarin Studioと C# 言語を用いて、 iOS と Android の両方の開発を行うことができます。