快速开发android应用5-使用picasso实现轮播图

概述

本次快速开发Android应用系列,是基于课工场的公开课高效Android工程师6周培养计划,记录微服私访APP的整个开发过程以及当中碰到的问题,供日后学习参考。

上一篇我们主要实现APP的主页界面的框架,使用viewpager+fragment来展现主页内容,使用BottomNavigationBar来完成页面的切换。
还没看过前一篇文章的朋友可以先去参考快速开发android应用4-使用viewpager+fragment构建主页

本篇主要通过picasso获取服务器图片,并通过轮播图的形式展现以及实现个人中心界面的展示。涉及到的项目功能点包括:

  • 如何使用picasso+viewpager展现轮播图
  • 怎么实现图片的自动跳转
  • 怎么实现图片的循环播放
  • 当用户滑动图片时,怎么停止自动轮播
  • 个人中心自定义SettingItemview的实现
    效果图:
    这里写图片描述

轮播图实现

图片获取

这里的图片是通过服务端的首页广告轮播接口获取的。

  1. 请求报文
    请求url:http://localhost:8080/visitshop/announcement
    请求方式:GET

  2. 响应报文

{
  "code": 0,
  "msg": "获取公告成功",
  "body": [
    {
      "id": 4,
      "imgUrl": "/visitshop//img/ann/ann4.jpg"
    },
    {
      "id": 3,
      "imgUrl": "/visitshop//img/ann/ann3.jpg"
    },
    {
      "id": 2,
      "imgUrl": "/visitshop//img/ann/ann2.jpg"
    },
    {
      "id": 1,
      "imgUrl": "/visitshop/img/ann/ann1.jpg"
    }
  ]
}


picasso展示图片

picasso是android大神 JakeWharton开发的一套图片缓存框架,文档上是这么介绍的。

A powerful image downloading and caching library for Android

使用起来也非常简单,只需要一行代码就可以搞定,真心nb

Picasso.with(context).load("http://i.imgur.com/DvpvklR.png").into(imageView);


在当前项目中,考虑到后续图片缓存框架的扩展性,对picasso做了一层封装,结合viewpager,实现轮播图效果。

第一步:添加picasso库依赖

Picasso.with(context).load("http://i.imgur.com/DvpvklR.png").into(imageView);


第二步:封装图片加载过程到一个单独的方法中

 /**
     * 根据地址获取图片
     * @param context
     * @param urls 图片访问地址
     * @param imgViews 显示图片的views
     * @return
     */
    public static void loadFromUrls(Context context, List<String> urls, List<ImageView> imgViews) {
        if (urls == null || urls.isEmpty()) {
            return;
        }
        int count = urls.size(); //图片数量
        if (imgViews == null || imgViews.isEmpty()) {
            return;
        }
        if (imgViews.size() < count) {
            count = imgViews.size();
        }

        for (int i = 0; i < count; i++) {
            Log.d(TAG, "Picasso load url:" + urls.get(i));
            Picasso picasso = new Picasso.Builder(context).build();
            if (mCacheFile != null && mCacheFile.exists()) {
                picasso = new Picasso.Builder(context)
                        .downloader(new OkHttp3Downloader(mCacheFile))
                        .build();
            }
            picasso.load(urls.get(i))
                    .into(imgViews.get(i));
        }
    }


第三步:使用viewpager.setPageAdapter方法,设置图片imageViewsviewpager中。

    private List<ImageView> mNoticeImgs = new ArrayList<>(); //公告图列表

    mViewPager = (ViewPager)view.findViewById(R.id.fragment_img_viewpager);
    mViewPager.setAdapter(mPagerAdapter);

    private PagerAdapter mPagerAdapter = new PagerAdapter() {
        @Override
        public int getCount() {
            return mNoticeImgs.size();
        }

        @Override
        public boolean isViewFromObject(View view, Object object) {
            return view == object;
        }

        @Override
        public void setPrimaryItem(ViewGroup container, int position, Object object) {
//            Log.d(TAG, "ImageViewPager - setPrimaryItem " + position);
            super.setPrimaryItem(container, position, object);
        }

        @Override
        public Object instantiateItem(ViewGroup container, int position) {
            Log.d(TAG, "ImageViewPager - instantiateItem " + position);
            ImageView view = mNoticeImgs.get(position);
            container.addView(view);
            return view;
        }

        @Override
        public Parcelable saveState() {
//            Log.d(TAG, "ImageViewPager - saveState");
            return super.saveState();
        }

        @Override
        public void restoreState(Parcelable state, ClassLoader loader) {
//            Log.d(TAG, "ImageViewPager - restoreState");
            super.restoreState(state, loader);
        }

        @Override
        public void startUpdate(ViewGroup container) {
//            Log.d(TAG, "ImageViewPager - startUpdate");
            super.startUpdate(container);
        }

        @Override
        public void destroyItem(ViewGroup container, int position, Object object) {
            Log.d(TAG, "ImageViewPager - destroyItem " + position);
            container.removeView(mNoticeImgs.get(position));
        }
    };


最后一步:就是通过网络请求获取图片请求地址列表,构建mNoticeImgs 数据,调用loadFromUrls并刷新界面即可,具体代码就不贴了,需要的可自行下载,会在文章最后的附录中提供。

    //网络请求、数据创建相关代码...

    ImageLoader.loadFromUrls(mActivity, urls, mNoticeImgs);
    //更新界面显示
    mPagerAdapter.notifyDataSetChanged();


picasso自定义缓存路径

Picasso默认的缓存路径位于data/data/your package name/cache/picasso-cache/下。开发过程中我们难免会遇到一些需求,需要我们去修改图片的缓存路径。
picasso的底层是通过okhttp去下载图片的。
早期的okhttp版本,可以直接使用picasso.download(new OkHttpDownloader(client))方法设置一个downloader,然后在okhttpclient.cache(new Cache(file, maxSize))中设置一个新的缓存路径。如下代码所示

        File file = new File("your cache path");
        if (!file.exists()) {
            file.mkdirs();
        }

        //设置图片缓存大小为运行时缓存的八分之一
        long maxSize = Runtime.getRuntime().maxMemory() / 8;
        OkHttpClient client = new OkHttpClient.Builder()
                .cache(new Cache(file, maxSize))
                .build();

        Picasso picasso = new Picasso.Builder(this)
                .downloader(new OkHttpDownloader(client))
                .build();


但okhttp3版本不支持这样设置downloader,那该怎么更改缓存路径呢?
为了解决上面描述的不能使用OkHttp3作为下载器的问题,jakewharton大神专门写了一个OkHttp3Downloader库,只要使用OkHttp3Downloader替换前面的OkHttpDownloader就能支持啦
获取OkHttp3Downloader库地址为:https://github.com/JakeWharton/picasso2-okhttp3-downloader

自动轮播图片

大致的思路是启动一个定时器,每隔一段时间更改当前所处的页面,就能实现自动轮播的效果了。
android中实现定时操作的方法有很多,这里使用timer和timertask实现

第一步: 自定义图片轮播task

    //自定义一个task,用于图片轮播 
    class AutoSlipTask extends TimerTask {

        @Override
        public void run() {
            Log.i(TAG, "AutoSlipTask start, curr page=" + mCurrPosition);
            if (mIsAutoSlide) {
                int temp = mNoticeImgs.size() - 2;
                if (mCurrPosition < 1) {
                    mCurrPosition = 1;
                }
                if (mCurrPosition > temp) {
                    mCurrPosition = temp;
                }
                mCurrPosition = (mCurrPosition + 1) % temp;
                Log.i(TAG, "slip page to " + mCurrPosition);
                mHandler.sendEmptyMessage(MSG_UPDATE_VEEWPAGE);
            }
        }
    }

这里要注意:图片切换操作(属于UI操作)要在主线程,否则会报错

  private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            if (msg.what == MSG_UPDATE_VEEWPAGE) {
                mViewPager.setCurrentItem(mCurrPosition);
                updataIndicator(mCurrPosition);
            }
        }
    };

第二步:启动定时器

    private void addAutoSlip() {
        if (mNoticeImgs.size() < 2) {
            return;
        }
        if (mTimer != null) {
            mTimer.cancel();
        }
        mTimer = new Timer("AutoSlipTimer");
        mTimer.schedule(new AutoSlipTask(), 3000, 3000);
    }

第三步:通过前两步已经实现自动轮播的效果了,这步主要是处理当用户离开homefragment,轮播不可见时,需要停止定时器;重新进入homefragment后,再重新启动。

    @Override
    public void onStart() {
        super.onStart();
        Log.d(TAG, "onStart - " + mFragmentName);
        //增加自动播放
        if (mTimer == null) {
            addAutoSlip();
        }
    }

    @Override
    public void onStop() {
        super.onStop();
        Log.d(TAG, "onStop - " + mFragmentName);
        if (mTimer != null) {
            mTimer.cancel();
            mTimer = null;
        }
    }


实现图片循环滑动

这里循环滑动指的是当用户滑到第一张图片后,再向前滑,则会跳转到最后一张图片; 滑到最后一张图片后,再向后滑,则会跳转到第一张图片;从而实现循环滑动的效果。

使用viewpager要实现这样的效果,网上大部分的思路是在当前图片基础上增加两张,分别放在第一页和最后一页,第一页放原来最后一张图片的内容,最后一页放原来第一张图片的内容。当滑到最后一页,则直接跳转到第二页(即原来第一张图片的位置);当滑到第一页,则直接跳转到倒数第二页(即原来最后一张图片的位置)。
这里写图片描述

代码实现过程

    /**
     * 增加循环滑动效果
     * @param requestUrls 请求的图片地址列表
     */
    private void addCycleSlip(List<String> requestUrls) {
        if (requestUrls.size() < 2) {
            return;
        }
        int len = requestUrls.size();
        String firstUrl = requestUrls.get(len - 1);
        String lastUrl = requestUrls.get(0);
        //在第一页前加最后一张图
        mNoticeImgs.add(0, buildImage(lastUrl));
        requestUrls.add(0, lastUrl);
        //在最后一页加第一张图
        mNoticeImgs.add(buildImage(firstUrl));
        requestUrls.add(firstUrl);
    }


还要注意一点,循环轮播,图片数增加了,但指示点数是不能增多的,并且当前所在图片位置也要跟着变换处理。

    private void updataIndicator(int position) {
        //清除所有指示下标
        mIndicatorLayout.removeAllViews();
        //需要去掉重复的第一页和最后一页
        for (int i = 1; i < mNoticeImgs.size() - 1; i++) {
            ImageView img = new ImageView(mActivity);
            //添加下标圆点参数
            LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
                    LinearLayout.LayoutParams.WRAP_CONTENT,
                    LinearLayout.LayoutParams.WRAP_CONTENT);
            params.leftMargin = 5;
            params.rightMargin = 5;
            img.setLayoutParams(params);
            img.setImageResource(R.drawable.sns_v2_page_point);
            if (i == position) {
                img.setSelected(true);
            }
            mIndicatorLayout.addView(img);
        }
    }


滑动页面时,指示点也要跟着变化

    @Override
    public void onPageSelected(int position) {
        Log.i(TAG, "onPageSelected - " + position);
        int size = mNoticeImgs.size();
        //右滑到最后一页
        if (position == size - 1) {
            mViewPager.setCurrentItem(1, false);
            return;
        }
        //左滑到第一页
        if (position == 0) {
            mViewPager.setCurrentItem(size - 2);
            return;
        }

        mCurrPosition = position;
        for (int i = 0; i < mIndicatorLayout.getChildCount(); i++) {
            ImageView image = (ImageView) mIndicatorLayout.getChildAt(i);
            if (i == position - 1) {
                image.setSelected(true);
            } else {
                image.setSelected(false);
            }
        }
    }


用户滑动时停止自动轮播

设置一个标志mIsAutoSlide,通过viewpager.setOnTouchListener()方法来获取用户的滑动事件,当用户在滑动时,不进行图片切换。

        mViewPager.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                Log.w(TAG, "receive event:" + event.getAction());
                switch (event.getAction()) {
                    case MotionEvent.ACTION_DOWN:
                        mIsAutoSlide = false;
                        break;
                    case MotionEvent.ACTION_MOVE:
                        mIsAutoSlide = false;
                        break;

                    case MotionEvent.ACTION_UP:
                        mIsAutoSlide = true;
                        break;
                }
                return false;
            }


当页面在滑动过程中,也不进行图片的切换

    @Override
    public void onPageScrollStateChanged(int state) {
        Log.i(TAG, "onPageScrollStateChanged - state=" + state);
        if (state == ViewPager.SCROLL_STATE_DRAGGING) {
            mIsAutoSlide = false;
        } else {
            mIsAutoSlide = true;
        }
    }


个人中心实现

自定义SetItemView

常用的自定义view的方式有:

  • 对系统原有的view进行组合,更方便使用
  • 继承原有的view,在原来的基础上进行扩展,比如说继承edittext,实现电话号码、银行卡号的输入,每隔几个字符就添加一个标记。
  • 继承view,重写onmeasure()、onlayout()、ondraw()方法,实现一个自定义view,这种一般用于实现自定义绘图。

这里的SetItemView使用的是第一种方式,具体如何实现呢。
首先,定义一个布局文件settingitem.xml文件

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
                android:id="@+id/rootLayout"
                android:layout_width="match_parent"
                android:layout_height="56dp"
                android:background="@drawable/bg"
                android:clickable="true"
                android:gravity="center_vertical">

    <ImageView
        android:id="@+id/iv_lefticon"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerVertical="true"
        android:layout_marginLeft="23dp"
        android:src="@drawable/clearcache"/>

    <TextView
        android:id="@+id/tv_lefttext"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_marginLeft="10dp"
        android:layout_toRightOf="@id/iv_lefticon"
        android:gravity="center_vertical"
        android:textSize="16sp"/>

    <ImageView
        android:id="@+id/iv_righticon"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentRight="true"
        android:layout_centerVertical="true"
        android:layout_gravity="center"
        android:layout_marginRight="23dp"
        android:src="@drawable/task_arrow"/>

    <View
        android:id="@+id/underline"
        android:layout_width="match_parent"
        android:layout_height="1px"
        android:layout_alignParentBottom="true"
        android:layout_marginLeft="16dp"
        android:layout_marginRight="16dp"
        android:background="#99999999"/>
</RelativeLayout>

然后,实现SetItemView,重写view构造方法

    public SetItemView(Context context) {
        this(context, null);
    }

    public SetItemView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public SetItemView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initView(context);
        getCustomStyle(context, attrs);
        //设置点击监听事件
        mRootLayout.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                if (null != mOnSetItemClick) {
                    mOnSetItemClick.click();
                }
            }
        });
    }


这里要实现自定义属性,需要新建一个attrs.xml文件,添加要定义的属性名称

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="SetItemView">
        <attr name="leftText" format="string"/>
        <attr name="leftIcon" format="integer"/>
        <attr name="rightIcon" format="integer"/>
        <attr name="textSize" format="float"/>
        <attr name="textColor" format="color"/>
        <attr name="isShowUnderLine" format="boolean"/>
    </declare-styleable>
</resources>

使用时,在xml根布局中声明 xmlns:app="http://schemas.android.com/apk/res-auto",然后使用app:的方式来使用自定义属性

<com.torch.chainmanage.view.SetItemView
    android:id="@+id/rl_me"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:isShowUnderLine="false"
    app:leftIcon="@drawable/medata"
    app:leftText="@string/fragment_me_tv_me"
    app:rightIcon="@drawable/task_arrow"
    app:textColor="@color/text_color_6"
    app:textSize="16"/>

最后,在创建SetItemView对象时,通过重写构造函数,得到自定义属性值,在完成SetItemView的初始化。

    /**
     * 初始化控件自定义属性信息
     *
     * @param context
     * @param attrs
     */
    private void getCustomStyle(Context context, AttributeSet attrs) {
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.SetItemView);
        ...
    }


附录

快速开发android应用相关的代码都会更新在我的github上,大家可以通过star来跟进项目代码的变动
https://github.com/youyutorch/RapidDevAndroid

参考资料:

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Android是一种常见的UI组件,用于在应用中展示多张片,并自动进行切换。以下是一种常见的Android代码实现方式: 1. 首先,在你的项目中添加一个ViewPager控件和一个指示器(Indicator)控件,用于展示片和指示当前片位置。 2. 创建一个适配器(Adapter)类,继承自PagerAdapter,并重写以下方法: - getCount():返回轮片数量。 - instantiateItem():创建并返回一个ImageView作为ViewPager的一个页面,并加载对应位置的片。 - destroyItem():销毁指定位置的页面。 3. 在Activity或Fragment中,初始化ViewPager和指示器,并设置适配器。 4. 在适配器中,使用一个集合来保存轮片资源或片URL。可以使用第三方库如Glide或Picasso来加载片。 5. 为了实现自动轮,可以使用Handler和Runnable来定时切换ViewPager的当前页面。在适配器中,可以通过重写getPageWidth()方法来设置每个页面的宽度,从而实现无限循环轮。 6. 为指示器添加相应的样式和逻辑,以显示当前页面的位置。 下面是一些相关问题: 1. 什么是ViewPager? 2. 如何使用Glide或Picasso加载片? 3. 如何实现无限循环轮? 4. 如何添加指示器(Indicator)控件? 5. 如何处理轮的点击事件? 请注意,以上只是一种常见的实现方式,实际上还有其他多种实现的方法。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值