欢迎访问移动开发之家(rcyd.net),关注移动开发教程。移动开发之家  移动开发问答|  每日更新
页面位置 : > > > 内容正文

快速开发android应用6-实现scrollview和recyclerview同方向滑动,, 上一篇我们主要实现通过

来源: 开发者 投稿于  被查看 22424 次 评论:28

快速开发android应用6-实现scrollview和recyclerview同方向滑动,, 上一篇我们主要实现通过


概述

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

上一篇我们主要实现通过picasso获取服务器图片,并通过轮播图的形式展现以及实现个人中心界面的展示。还没看过前一篇文章的朋友可以先去参考快速开发android应用5-使用picasso实现轮播图 

本篇我们主要实现首页最新任务、最新资讯的获取与展示,以及巡店页面历史巡店数据的获取、展示和搜索功能。涉及到的项目知识点包括:

  • 使用recyclerview展示任务及资讯信息

  • 解决scrollerviewrecyclerview滑动冲突,实现同屏滑动

  • 使用xrecyclerview展示历史巡店信息

  • 通过关键字搜索历史巡店信息 
    效果图: 
    这里写图片描述 

首页资讯、任务获取展示

获取数据

任务获取接口

 1. 请求报文 
请求url:http://localhost:8080/visitshop/task?pagenum=1 
请求类型:GET

 2. 响应报文

{
  "code": 0,
  "msg": "任务信息获取成功",
  "body": [
    {
      "title": "新产品Y008调研",
      "detail": "针对公司新产品Y008的市场调研。需要来店里咨询的客户填写问卷,并留联系方式。问卷已发送至各位邮箱,请下载并打印。",
      "publishdate": "2016-08-15",
      "executedate": "2016-09-30",
      "state": 0
    },
    {
      "title": "用户反馈统计",
      "detail": "需要来店里咨询的用户统计,统计用户相关信息,信息表需要到公司网站下载。",
      "publishdate": "2016-07-15",
      "executedate": "2016-10-11",
      "state": 0
    },
    {
      "title": "关于意见反馈",
      "detail": "现在开通提意见、赢大奖活动,可以提出自己发现目前存在的问题,或者有其他的建议,一经采纳给予奖励。意见通道可以通过APP意见反馈提交",
      "publishdate": "2016-07-11",
      "executedate": "2016-08-11",
      "state": 0
    },
    {
      "title": "优秀员工评选",
      "detail": "评选优秀员工,需要个员工登录公司网站进行投票,没有投票的视为弃权。",
      "publishdate": "2016-05-10",
      "executedate": "2016-06-01",
      "state": 0
    },
    {
      "title": "回收旧产品,以旧换新",
      "detail": "公司现在退出以旧换新活动,相关说明查看公司网站活动模块。",
      "publishdate": "2016-03-15",
      "executedate": "2016-07-10",
      "state": 1
    },
    {
      "title": "新产品XS03型号宣传推广",
      "detail": "新产品XS03型号的宣传,每个店面需要放置相关产品在主要位置,店面门口需要有推介海报。展示产品需要有专人负责讲解,会进行不定期抽查。",
      "publishdate": "2016-03-01",
      "executedate": "2016-06-01",
      "state": 1
    },
    {
      "title": "年度总结",
      "detail": "各店面对去年一年工作总结,包括销售业绩、发现的问题、解决方式,经理需要对每个员工做出考核评价",
      "publishdate": "2016-01-05",
      "executedate": "2016-01-25",
      "state": 1
    }
  ]
}

资讯获取接口

  1. 请求报文 
请求url:http://localhost:8080/visitshop/info?pagenum=1&type=0 
请求类型:GET

 2. 响应报文

{
  "code": 0,
  "msg": "资讯信息获取成功",
  "body": [
    {
      "title": "华为联想全球化启示:如何在海外构建中国品牌",
      "summary": "腾讯科技",
      "imgurl": "http://mat1.gtimg.com/tech/00Jamesdu/2014/index/remark/2.png",
      "detail": "http://tech.qq.com/a/20151123/008196.htm"
    },
    {
      "title": "联想取消中高端手机品牌VIBE",
      "summary": "腾讯科技",
      "imgurl": "http://mat1.gtimg.com/tech/00Jamesdu/2014/index/remark/2.png",
      "detail": "http://tech.qq.com/a/20151123/018308.htm"
    },
    {
      "title": "联想计划明年在印度生产1000万部手机",
      "summary": "腾讯科技",
      "imgurl": "http://img1.gtimg.com/tech/pics/hv1/101/186/1974/128406881.jpg",
      "detail": "http://tech.qq.com/a/20151126/043557.htm"
    },
    {
      "title": "联想签约高通:中国手机产业躲不过专利费",
      "summary": "腾讯科技",
      "imgurl": "http://img1.gtimg.com/tech/pics/hv1/165/235/2024/131670690.jpg",
      "detail": "http://tech.qq.com/a/20160224/030848.htm"
    },
    {
      "title": "联想:非洲是下个最大手机市场 超印度和中国",
      "summary": "腾讯科技",
      "imgurl": "http://img1.gtimg.com/tech/pics/hv1/224/96/2026/131765354.jpg",
      "detail": "http://tech.qq.com/a/20160226/044907.htm"
    },
    {
      "title": "众创空间WeWork融资4.3亿美元 联想控股领投",
      "summary": "腾讯科技",
      "imgurl": "http://img1.gtimg.com/tech/pics/hv1/223/117/2033/132225883.jpg",
      "detail": "http://tech.qq.com/a/20160310/025118.htm"
    },
    {
      "title": "陈旭东公开信:联想将在国内扭转智能手机业务",
      "summary": "腾讯科技",
      "imgurl": "http://mat1.gtimg.com/tech/00Jamesdu/2014/index/remark/2.png",
      "detail": "http://tech.qq.com/a/20160318/043508.htm"
    },
    {
      "title": "小米华为联想魅族推出的千元机,都不是自己设计的",
      "summary": "网易新闻",
      "imgurl": "http://inews.gtimg.com/newsapp_ls/0/305511207_300240/0",
      "detail": "http://tech.qq.com/a/20160518/076472.htm"
    },
    {
      "title": "联想发布模块化手机Moto Z 投影、摄影、背壳能自选",
      "summary": "腾讯科技",
      "imgurl": "http://inews.gtimg.com/newsapp_ls/0/555495485_300240/0",
      "detail": "http://tech.qq.com/a/20160906/038980.htm"
    },
    {
      "title": "联想的AR手机延期上市,智能手机找点“创新”真不容易",
      "summary": "腾讯科技",
      "imgurl": "http://inews.gtimg.com/newsapp_ls/0/580125438_300240/0",
      "detail": "http://tech.qq.com/a/20160914/009726.htm"
    }
  ]
}

使用RecyclerView展现数据

以前展现列表数据,首先就会想到ListView,使用过ListView的人都碰到过滑动困顿,点击事件混乱,布局不灵活等问题。基于以上几点google推出了RecyclerView:ListView的升级版,它不仅解决了原来在ListView上存在的问题,而且布局更灵活,同时提高了效率。 
使用RecyclerView的方法和ListView是类似的。 

第一步,获取数据源,以获取资讯数据为例

private void requestInfo() {
        Log.i(TAG, "requestInfo - 请求获取资讯");
        String url = RequestUrl.Info + "?pagenum=1&type=0"; //获取公司第一页动态
        OkHttpHelper.getInstance().doGet(url, new OkHttpHelper.RequestCallback() {
            @Override
            public void onSuccess(String result) {
                Log.d(TAG, "requestInfo onSuccess 成功获取资讯 - " + result);

                mInfoList = GsonUtil.parseInfoJson(result);
                if (mInfoList == null) {
                    //从数据库中读取
                    mInfoList = DataSupport.findAll(Info.class);
                } else {
                    //更新到数据库
                    DataSupport.deleteAll(Info.class);
                    DataSupport.saveAll(mInfoList);
                }
                showInfoList(mInfoList);
            }

            @Override
            public void onFailure(IOException e) {
                Log.w(TAG, "requestInfo onFailure 获取资讯失败");
                e.printStackTrace();

                //从数据库中读取
                mInfoList = DataSupport.findAll(Info.class);
                showInfoList(mInfoList);
            }
        });
    }

第二步,创建一个Adapter实例,以资讯列表的InfoListBaseAdapter为例。

/**
 * HomeFragment资讯列表适配器
 */
public class InfoListBaseAdapter extends RecyclerView.Adapter<InfoListBaseAdapter.InfoViewHolder> {
    private Context mContext;
    private List<Info> list;
    int number;

    public InfoListBaseAdapter(Context mContxt, List<Info> list, int number) {
        this.mContext = mContxt;
        this.list = list;
        this.number = number;
    }

    @Override
    public InfoViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view = View.inflate(mContext, R.layout.fragment_home_info_item, null);
        return new InfoViewHolder(view);
    }

    @Override
    public void onBindViewHolder(InfoViewHolder holder, final int position) {
        Info rd = list.get(position);
        holder.title.setText(rd.getTitle());
        holder.context.setText(rd.getSummary());
        if (!"".equals(rd.getImgurl().trim()) && rd.getImgurl() != null) {
            Picasso.with(mContext).load(rd.getImgurl()).into(holder.img);
        }
        holder.root.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //跳转到资讯详情
                Toast.makeText(mContext, "资讯详情界面敬请期待" + position, Toast.LENGTH_SHORT).show();
            }
        });
    }

    @Override
    public int getItemCount() {
        //显示list的前几条数据
//        if (list.size() >= number) {
//            return number;
//        } else {
            return list.size();
//        }
    }

    class InfoViewHolder extends RecyclerView.ViewHolder {
        ImageView img, arrow;
        TextView title, context;
        RelativeLayout root;

        public InfoViewHolder(View itemView) {
            super(itemView);
            arrow = (ImageView) itemView.findViewById(R.id.fragment_home_info_item_arrow);
            img = (ImageView) itemView.findViewById(R.id.fragment_home_info_item_img);
            context = (TextView) itemView.findViewById(R.id.fragment_home_info_item_context);
            title = (TextView) itemView.findViewById(R.id.fragment_home_info_item_title);
            root = (RelativeLayout) itemView.findViewById(R.id.item_root_home);
        }
    }

}
  • 这里的InfoListBaseAdapter.InfoViewHolder继承自RecyclerView.ViewHolder,它的作用和使用ListView写的自定义ViewHolder的作用相同,都是再重新获取itemview实例时,不需要再调用findViewById去找个各个子view,提高效率。

  • 在onCreateViewHolder()方法初始化view,然后在onBindViewHolder()绑定具体的position,绑定相关的数据。

  • 与ListView不同,RecyclerView是不能通过setOnItemClickListener()方法去设置item click事件,只能通过类似item.setOnClickListener()方法去设置。


第三步,初始化RecyclerView,设置适配器、布局管理器、是否需要动画等。

//获取对象
        mInfoRecyclerView = (RecyclerView) view.findViewById(R.id.fragment_home_info_list);
       //设置布局管理器   
       LinearLayoutManager infoLayoutManager = new LinearLayoutManager(mActivity, LinearLayoutManager.VERTICAL, false);
       //设置适配器
                   mInfoRecyclerView.setLayoutManager(infoLayoutManager);

解决滑动冲突

当前主页的布局是一个scrollview嵌套着两个recyclerview(一个显示任务列表,一个显示资讯列表),scrollviewrecyclerview都是可以上下滑动的,那怎么解决它们的滑动冲突问题呢? 
第一种方法recyclerview数据量较少,比如说我只显示任务列表的前三条数据,那我们可以直接设置recyclerview不能滑动,全权把上下滑动事件交给scrollview处理。

//设置task recyclerview不可滑动
mTaskRecyclerView.setNestedScrollingEnabled(false);

第二种方法,设置scrollviewrecyclerview都是可滑动的,当上下滑动事件在recyclerview的区域时,那就滑动recyclerview;当不在recyclerview的区域时,就滑动scrollview。 

要实现scrollviewrecyclerview都可以滑动的效果,首先我们要了解scrollview具体是如何工作的?

原因

当事件分发到scrollview,会调用其中的onInterceptTouchEvent()方法,在这里scrollview会去做判断,若当前页面可滑动且用户正在做上下滑动,则截取这个事件,交由自己的方法onTouchEvent()处理。

@Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        /*
         * This method JUST determines whether we want to intercept the motion.
         * If we return true, onMotionEvent will be called and we do the actual
         * scrolling there.
         */

        /*
        * Shortcut the most recurring case: the user is in the dragging
        * state and he is moving his finger.  We want to intercept this
        * motion.
        */
        final int action = ev.getAction();
        if ((action == MotionEvent.ACTION_MOVE) && (mIsBeingDragged)) {
            return true;
        }

        if (super.onInterceptTouchEvent(ev)) {
            return true;
        }
        ...
    }

这也解释了,为什么不能对recyclerview进行滑动的原因,因为滑动事件已经被scrollview截取了,recyclerview压根没有收到滑动事件,肯定不会滑动了。

解决方法

了解了这个原因之后,我们就可以通过重写scrollview 的 onInterceptTouchEvent()方法来重新判断滑动事件是否要截取。 

这里以资讯列表的mTaskRecyclerView为例,设置mTaskRecyclerView的高度值固定,当scrollview 滑动最底端(即滑动值getScrollY达到最大值时),不再截取上下滑动事件。

@Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        if (getScrollY() >= (getMaxScrollAmount() - 20)) {
            //不再截断,将滑动事件交给子view处理
            return false;
        }
        return super.onInterceptTouchEvent(ev);
    }

当加上这段代码后,scrollview滑到最底端后,因为getScrollY()值不变,所有的上下滑动事件都交给子view mTaskRecyclerView了,导致不能重新滑到顶端。 

为了能重新截取到上下滑动事件,需要根据ev.getRawY()方法获取当前触摸事件所在的位置,如果不在mTaskRecyclerView的区域内,则重新截取上下滑动事件。

@Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        Log.d(TAG, "onInterceptTouchEvent - getScrollY()=" + getScrollY() + ", getMaxScrollAmount()=" + getMaxScrollAmount());
        Log.d(TAG, "onInterceptTouchEvent - ev.getRawY()=" + ev.getRawY());
        Log.d(TAG, "onInterceptTouchEvent - mMaxScreenY=" + mMaxScreenY);
        if (getScrollY() >= (getMaxScrollAmount() - 20)) {
            if (mMaxScreenY == 0) {
                mMaxScreenY = mListener.getMaxScreenY();
            }
            mIsInBottomArea = ev.getRawY() > mMaxScreenY;
            if (mIsInBottomArea) {
                //不再截断,将滑动事件交给子view处理
                return false;
            }
        }
        return super.onInterceptTouchEvent(ev);
    }

这里的mMaxScreenY 获取的是当scrollview滑到最底端后,mTaskRecyclerView的最小Y坐标。

public interface ScreenListener {
        int getMaxScreenY(); //获取能滑动的最大Y坐标
    }

        private void initScreenListener() {
        mPartScrollView.setScreenListener(new PartScrollView.ScreenListener() {
            @Override
            public int getMaxScreenY() {
                int height = mTaskRecyclerView.getHeight();
                int[] location = new int[]{0, 0};
                mTaskRecyclerView.getLocationOnScreen(location);
                Log.d(TAG, "getMaxScreenY - location[1]=" + location[1] + ",height=" + height);
                return location[1] + height;
            }
        });
    }

XRecyclerView展现历史巡店列表数据

获取数据

  1. 请求报文 
    请求url:http://localhost:8080/visitshop/history?userid=num01&pagenum=1 
    请求类型:GET

  2. 响应报文

{
  "code": 0,
  "msg": "历史巡店查询成功",
  "page": 1,
  "datelist": [
    {
      "id": 1,
      "visitdate": "2016-10-20",
      "shopid": "WFSF75",
      "shoplocation": "中国北京市海淀区成府路207号",
      "userid": "num01",
      "shoplevel": "5;5;5",
      "feedback": "店面整洁,人员精神饱满,没有发现问题",
      "name": "北京新中关购物中心店",
      "imgpath": "/visitshop/img/visit/2016-10-20/",
      "imgname": "1476951920804_1.jpg;1476951920805_2.jpg"
    }
  ]
}

数据展现

XRecyclerView是RecyclerView的升级版,它是在RecyclerView的基础上增加:

  • 增加上拉刷新、下载加载更多自定义控件

  • 增加获取不到数据时展现EmptyView

  • 增加自定义控件加载风格、自定义图片

具体使用方法也和RecyclerView类似,在RecyclerView的基础上,增加自定义方法setLoadingListener()、setEmptyView()等。

recyclerView = (XRecyclerView) view.findViewById(R.id.activity_visitshop_list);
        recyclerView.setLoadingListener(this);
        //设置加载风格
        recyclerView.setLoadingMoreProgressStyle(ProgressStyle.SquareSpin);
        recyclerView.setRefreshProgressStyle(ProgressStyle.BallSpinFadeLoader);
        //设置线性列表展示
        recyclerView.setLayoutManager(
                new LinearLayoutManager(mActivity, LinearLayoutManager.VERTICAL, false));
        recyclerView.setAdapter(adapter);
        //设置空布局
        View emptyView = view.findViewById(R.id.activity_visitshop_none);
        emptyView.setOnClickListener(this);
        recyclerView.setEmptyView(emptyView);

    @Override
    public void onRefresh() {
        //下拉刷新
        pagenum = 1;
        initData();
    }

    @Override
    public void onLoadMore() {
        //加载更多
        initData();
    }

还有其他一些用法如设置自定义加载风格,设置自定义图片,想了解的可以上github上看一下具体的使用说明。 

搜索框的实现

这里,直接使用EditText 的 setOnEditorActionListener()方法来实现,通过重载onEditorAction()来获取搜索点击的事件,并做相应的处理。

@Override
    public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
        /**
         * 当点击搜索按钮时
         */
        if (actionId == EditorInfo.IME_ACTION_SEARCH) {
            hideKeyboard();
            shop_name = search.getText().toString().trim();
            progress.setVisibility(View.VISIBLE);
            pagenum = 1;
            //店面查询请求
            String urlString = RequestUrl.HistroyShop + "?userid=" + userid + "&pagenum=" + pagenum + "&shopName=" + shop_name;
            OkHttpHelper.getInstance().doGet(urlString, new OkHttpHelper.RequestCallback() {
                @Override
                public void onSuccess(String result) {
                    getShopSuccess(result);
                }

                @Override
                public void onFailure(IOException e) {
                    getShopFailed();
                }
            });
            IsSearch = true;
        }
        return false;
    }

附录

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

参考资料:

  • Android事件分发机制完全解析,带你从源码的角度彻底理解

  • xrecyclerview的github开源地址

  • 拆解轮子之XRecyclerView

  • ScrollView源码分析



作者:youyu_torch

用户评论