今天在准备新项目的界面,偶然翻到了QQ附近的人那个筛选功能,嗯,觉得效果还不错,效果大概是这样子的。QQ的原图我就不上了,我就上我做的效果图。

觉得so easy是吧,但是我整整做了4个多小时,个多小时,多小时,小时,时。。。。

唉,苦话不多说,先分析一下界面。

主要功能分析

1.一个带选择框的recyclerView
2.根据位置不同透明度以及大小的item
3.停止滑动的时候会将中点对应的item适应到选择框内。
4.非手势滑动同样需要改变item的透明度及大小

界面分析

这个就没啥好说了,一个RecyclerView而已,如果有人不知道什么是RecyclerView,那么没关系,你只要知道这是android官方推出的listview的升级版就够了。什么?你不知道什么是listview?那你还是先去了解完android基础后再来看看吧。。

功能实现

看一眼布局还有MainActivity
MainActivity
public class MainActivity extends AppCompatActivity
{FocusRecyclerView mRecyclerView;private List<String> mDatas = new ArrayList<>();@Overrideprotected void onCreate(Bundle savedInstanceState){super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);mDatas = new ArrayList<>();for (int i = 0; i < 30; i++){mDatas.add("嘻嘻:" + i);}mRecyclerView = (FocusRecyclerView) findViewById(R.id.recyclerView);mRecyclerView.setAdapter(new RecyclerView.Adapter<MyViewHolder>(){@Overridepublic MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType){View view = LayoutInflater.from(MainActivity.this).inflate(R.layout.recyclerview_item, parent, false);MyViewHolder viewHolder = new MyViewHolder(view);return viewHolder;}@Overridepublic void onBindViewHolder(MyViewHolder holder, int position){holder.textView.setText(mDatas.get(position));}@Overridepublic int getItemCount(){return mDatas.size();}});mRecyclerView.setLayoutManager(new SnappingLinearLayoutManager(this, LinearLayoutManager.VERTICAL,false));}class MyViewHolder extends RecyclerView.ViewHolder{private TextView textView;public MyViewHolder(View itemView){super(itemView);textView = (TextView) itemView.findViewById(R.id.tv);}}}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayoutxmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"tools:context="com.chenantao.main.MainActivity"><com.chenantao.main.FocusRecyclerViewandroid:id="@+id/recyclerView"android:layout_width="match_parent"android:layout_height="match_parent"/>
</RelativeLayout>
最后再看一下item的布局recyclerview_item.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"><TextViewandroid:id="@+id/tv"android:text="嘻嘻"android:layout_width="wrap_content"android:layout_height="wrap_content"android:gravity="center"android:textSize="25sp"/>
</LinearLayout>
这个虽然我外面包了LinearLayout,但其实用Merge包着更好。

自定义RecyclerView

重头戏来了。今天下午刚看到这个效果的时候,网上搜了一下,没搜到啥,不过看到有个哥们用ScrollView实现了类似的效果,不过ScrollView没有复用机制,不适合加载比较多的数据,那我们还是用RecyclerView来实现吧。先看一下需要的变量以及在构造方法中初始化它们。
        public static final int DISPLAY_ITEM_COUNT = 5;//只能同时展示5条数据,注意,只能展示奇数条数据private int mMaxTextSize = 30, mMinTextSize = 10;//sp,textview字体大小最大以及最小限制private double mMaxTextAlpha = 0.9, mMinTextAlpha = 0.4;//textview透明度最大以及最小限制private Paint mSelectedBorderPaint;//绘制选择框的paintprivate SnappingLinearLayoutManager mLayoutManager;//自定义的布局管理器,可以缓慢滑动到指定位置private double mTextSizeScale, mTextAlphaScale;//字体大小以及透明度的梯度值public FocusRecyclerView(Context context, AttributeSet attrs){super(context, attrs);//将dp转换为pxmMaxTextSize = (int) DimensionUtils.sp2px(mMaxTextSize, getResources().getDisplayMetrics());mMinTextSize = (int) DimensionUtils.sp2px(mMinTextSize, getResources().getDisplayMetrics());mSelectedBorderPaint = new Paint();mSelectedBorderPaint.setColor(0x6600BFFF);mSelectedBorderPaint.setAntiAlias(true);mSelectedBorderPaint.setStyle(Paint.Style.STROKE);mSelectedBorderPaint.setStrokeWidth(3);}
为了提高灵活性,我们把控制透明度以及字体大小的变量提取出来,同样你也可以抽取出自定义属性供外部设置,这里我就不弄了。还有,关于梯度值如果有童鞋不知道的,你看完下面的代码就会知道了。

绘制选择框

细心的童鞋估计已经发现了,这选择框是固定在中间的,同时它的大小跟每一项item的高度是一样的。那么绘制这种事肯定是放在OnDraw()方法里面完成了,很简单,就是绘制个矩形而已,当然高度需要根据item的高度动态计算。
         /*** 画出选择框** @param c*/@Overridepublic void onDraw(Canvas c){super.onDraw(c);int width = getMeasuredWidth();int height = getMeasuredHeight();int itemHeight = height / DISPLAY_ITEM_COUNT;int t = (height - itemHeight) / 2;Rect rect = new Rect(0, t, width, t + itemHeight);c.drawRect(rect, mSelectedBorderPaint);}
根据屏幕的高度以及需要展示的item的数量,就能得出每个item的高度了,然后根据这个高度绘制矩形就够了,这个太简单。

滑动到中点对应的item

嗯,就是这个比较难了。首先,你需要得到在滑动停止的时候得到中点对应的view。然后,你要得到这个view在RecyclerView中的位置。最后,根据这个位置缓慢滑动到对应的位置,当然,这个位置肯定不是中点view的位置,RecyclerView并没有提供滑动到中点的方法。要滑动到的目标位置是中点的位置centerPos-(DISPLAY_ITEM_COUNT-1)/2,因为我把展示的item数量定义为奇数,所以这些条目在屏幕中其实是对称的,那么这样计算就能拿要滑动的位置了。
说起来太复杂,看代码吧。
/*** 在滑动停止的时候设置选中的条目** @param state*/@Overridepublic void onScrollStateChanged(int state){super.onScrollStateChanged(state);if (state == SCROLL_STATE_IDLE){scrollToSelectedView();}}
    /*** 滑动到目前选择框选中的item*/private void scrollToSelectedView(){//取得中点对应的item,类似于listview的pointToPositionView selectView = findChildViewUnder(getMeasuredWidth() / 2, getMeasuredHeight() / 2);int pos = getChildAdapterPosition(selectView);mLayoutManager.smoothScrollToPosition(this, null, pos - ((DISPLAY_ITEM_COUNT - 1) / 2));}
看起来挺简单是吧,但是实际上你这样做的时候不行,为什么呢?你如果做过实际测试你会发现,有时滑动的时候,第一个view的顶部并不会对齐parent的顶部,这是非常严重的问题,这会导致中点的item不能很好的装进选择框里面。那么有什么改变方法呢?还好,RecyclerView的布局管理器提供了一个非常方便的方法:scrollToPositionWithOffset(pos,offset)。这是个神马方法呢,这个方法提供两个参数,一个是滑动到的位置,一个是偏移量。什么意思呢,就是如果我指定一个我要滑动到的目标位置,同时指定偏移量为0,那么这个目标位置的view的顶部便会紧紧的对齐parent的顶部。修改后的代码是下面这样。
/*** 滑动到目前选择框选中的item*/private void scrollToSelectedView(){//取得中点对应的item,类似于listview的pointToPositionView selectView = findChildViewUnder(getMeasuredWidth() / 2, getMeasuredHeight() / 2);int pos = getChildAdapterPosition(selectView);
//      mLayoutManager.smoothScrollToPosition(this, null, pos - ((DISPLAY_ITEM_COUNT - 1) / 2));mLayoutManager.scrollToPositionWithOffset(pos - ((DISPLAY_ITEM_COUNT - 1) / 2),0);}
到这里,你可以试试,差不多就能达到效果了,这里我就不放效果图了。但是,有一个问题。这尼玛这瞬间滑动是什么鬼?这一点也不优雅,太突兀了吧,那么是否有提供类似listview的smoothScrollTo()的方法呢?木有,真的木有。。那么怎么办呢?我查了好多资料,终于在StackOverFlow发现了一位大婶的解决方法,解决方法就是自定义布局管理器,在里面可以随意的改变它的滑动速度,代码如下
public class SnappingLinearLayoutManager extends LinearLayoutManager
{private static final float MILLISECONDS_PER_INCH = 500f;private Context mContext;public SnappingLinearLayoutManager(Context context, int orientation, boolean reverseLayout){super(context, orientation, reverseLayout);}@Overridepublic void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state,int position){RecyclerView.SmoothScroller smoothScroller = new TopSnappedSmoothScroller(recyclerView.getContext()){//This controls the direction in which smoothScroll looks for your view@Overridepublic PointF computeScrollVectorForPosition(int targetPosition){return new PointF(0, 1);}//This returns the milliseconds it takes to scroll one pixel.@Overrideprotected float calculateSpeedPerPixel(DisplayMetrics displayMetrics){return MILLISECONDS_PER_INCH / displayMetrics.densityDpi;}};smoothScroller.setTargetPosition(position);startSmoothScroll(smoothScroller);}private class TopSnappedSmoothScroller extends LinearSmoothScroller{public TopSnappedSmoothScroller(Context context){super(context);}@Overridepublic PointF computeScrollVectorForPosition(int targetPosition){return SnappingLinearLayoutManager.this.computeScrollVectorForPosition(targetPosition);}@Overrideprotected int getVerticalSnapPreference(){return SNAP_TO_START;}}
}
嗯,在MainActivity对RecyclerView应用这个布局管理器后,再重新修改一下代码,如下
/*** 滑动到目前选择框选中的item*/private void scrollToSelectedView(){//取得中点对应的item,类似于listview的pointToPositionView selectView = findChildViewUnder(getMeasuredWidth() / 2, getMeasuredHeight() / 2);int pos = getChildAdapterPosition(selectView);mLayoutManager.smoothScrollToPosition(this, null, pos - ((DISPLAY_ITEM_COUNT - 1) / 2));}

其实就跟第一次的时候一样了,到这里就完成了,可以自动将中点的item装入到选择框。那么接下来的事情就简单了,无非就是改改透明度字体大小。

随着距离而改变的item

分析一下这个需求,我们首先得得到视野内的所有item,然后根据item距离中点的距离来设置字体大小以及透明度。在这个例子中我们的需求是,距离中点越远,字体越小,透明度越低。Ok,明白这些后,我们看一下代码。
/*** 设置可视recyclerView可视item的属性(字体大小、透明度)*/public void setItemStyle(){int firstVisiblePos = mLayoutManager.findFirstVisibleItemPosition();int lastVisiblePos = mLayoutManager.findLastVisibleItemPosition();for (int i = firstVisiblePos; i <= lastVisiblePos; i++){View view = mLayoutManager.findViewByPosition(i);int itemHeight = getMeasuredHeight() / DISPLAY_ITEM_COUNT;int x = (int) view.getX();int y = (int) view.getY();//itemCenter:item的中点,相对于parent的y距离int itemCenter = y + itemHeight / 2;if (itemCenter < 0) itemCenter = 0;//distanceToCenter:item的中点距离parent的y距离int distanceToCenter = Math.abs(getMeasuredHeight() / 2 - itemCenter);TextView textview = (TextView) view.findViewById(R.id.tv);//距离中点最近的时候透明度最大,字体也最大//设置textview字体大小随着距离中点距离的改变而改变textview.setTextSize((float) (mMaxTextSize - mTextSizeScale * distanceToCenter));//设置textview透明度随着距离中点距离的改变而改变textview.setAlpha((float) (mMaxTextAlpha - mTextAlphaScale * distanceToCenter));}}

嗯,根据第一个可视item的位置以及最后一个最后可视item的位置来遍历可视的view。然后计算这些view距离中点的距离,在结合前面定义的梯度值来设置字体大小与透明度,关于这个梯度值,你看最后两行你也明白了,其实也可以理解成根据距离来决定变化幅度的因子,这个你想怎么决定就怎么决定,没有什么要求的。ok,到这里基本就完成了,还有什么得注意的呢?在哪里调用这个setItemStyle方法呢?我们滑动的时候这些item的样子也要跟着变化吧?首次加载显式出来的时候也要有样子吧?

那么写在onTouchEvent方法中可以吗?答案是:不可以,如果写在onTouchEvent,那么你大力一滑,然后松手,页面是有在继续滚动,只是。。 item的样式貌似没变吧。。那么应该写在哪里呢,有人早就知道啦,那就是写在onScroll方法里了。这样只要一滑动,item的样式就都会更新了。那么,看代码
@Overridepublic void onScrolled(int dx, int dy){super.onScrolled(dx, dy);setItemStyle();}
那么还没滑动的时候,设置样式要在哪里设置呢?那么我们想一下,设置这些需要LayoutManager,以及所有子view的纵坐标,那么写在哪里最合适呢,我觉得是onLayout吧。
@Overrideprotected void onLayout(boolean changed, int l, int t, int r, int b){super.onLayout(changed, l, t, r, b);if (changed){if (mLayoutManager != null){setItemStyle();}}}

注意这里要判断一下布局管理器设置进来了没,因为你就算还没设置布局管理器,它还是会调用onMeasure、onLayout的。这样可能会空指针异常。Ok,到这里就完了。

完结

看起来没多少行代码是吧,我也觉得是,但是我 真 !心 !写 !了 !好!久。不过我写后滑了几下,越来越感觉不对劲,这咋尼玛这么像一个原生控件,想了一下,这难道不是datapicker?唉,本来得先去看一下它怎么实现的。。不过都写好了,心好累,懒得看了。
觉得这样算完了吗?没完呢,你还可以改进的地方有,支持显式偶数个item,自适应的字体大小。更多精彩,等着你自己发挥。
各位看官,不管喜欢不喜欢,踩一下呗。。

End!~

山寨风,高仿QQ附近的人筛选功能的滑动选择列表来袭!相关推荐

  1. Android实现高仿QQ附近的人搜索展示

    本文主要实现了高仿QQ附近的人搜索展示,用到了自定义控件的方法 最终效果如下 1.下面展示列表我们可以使用ViewPager来实现(当然如果你不觉得麻烦,你也可以用HorizontalScrollVi ...

  2. 安卓高仿京东淘宝的筛选功能,全网最简单方便

    最近需要实现一个筛选的功能.但是在网上找了半天都写得特别乱,要不就是收费,今天做一个.实现效果图如下. 点击按钮直接弹出.可以放在任何控件下面.下面是使用代码: 首先需要添加依赖: 在build.gr ...

  3. 高仿QQ的手机管家的小火箭加速

    高仿QQ的手机管家的小火箭加速 1.前言 腾讯的手机管家,用过这个App的人都应该知道桌面的火箭一键加速这个功能,研究一下这个小火箭是怎么做出来的.先来了解一下小火箭有神马动作,首先在没有触碰它时,就 ...

  4. android 自定义特效,Android自定义View之高仿QQ健康

    我们都知道自定义View一般有三种直接继承View.继承原有的控件对控件的进行修改.重新拼装组合,最后一种主要针对于ViewGroup.具体的怎么做不是本文的所涉及的内容(本文是基于第一种方式实现的) ...

  5. 高仿qq搜索附近界面

    首页 资讯 文章 频道 资源 小组 相亲 登录 注册 首页 所有文章 行业动态 技术分享 产品设计 工具资源 安卓小组 更多频道▼ - 导航条 - 首页所有文章 行业动态技术分享 产品设计 工具资源安 ...

  6. 如何使用MFC编写自定义UI界面【附高仿QQ 2014登陆界面范例程序】

    地址: http://blog.csdn.net/hujkay作者:Jekkay Hu(34538980@qq.com)关键词:MFC, 编写异行窗体,自定义UI控件,VC++,异形控件,高仿QQ登陆 ...

  7. 高仿QQ即时聊天软件开发系列之三登录窗口用户选择下拉框

    上一篇高仿QQ即时聊天软件开发系列之二登录窗口界面写了一个大概的布局和原理 这一篇详细说下拉框的实现原理 先上最终效果图 一开始其实只是想给下拉框加一个placeholder效果,让下拉框在未选择未输 ...

  8. android qq红点,Android高仿QQ小红点功能

    先给大家展示下效果图: 绘制贝塞尔曲线: 主要是当在一定范围内拖拽时算出固定圆和拖拽圆的外切直线以及对应的切点,就可以通过path.quadTo()来绘制二阶贝塞尔曲线了~ 整体思路: 1.当小红点静 ...

  9. android+qq底部界面,Android 高仿QQ 界面滑动效果

    Android高仿QQ界面滑动效果 点击或者滑动切换画面,用ViewPager实现, 首先是布局文件: android:layout_width="match_parent" an ...

最新文章

  1. 利用反射计芯片进行非接触式液位测量
  2. REST API出错响应的设计(转)
  3. LindDotNetCore~Mock对实际应用中的意义
  4. boost::interprocess::ipcdetail::private_adaptive_node_pool用法的测试程序
  5. java中for的常规用法_Java for循环的几种用法详解
  6. 『飞秋』WCF热门问题编程示例
  7. 2c总线的布线宽度_PCIExpress总线接口的布线规则
  8. keepalived vrrp script|interface weight when positive,nagtive,zero vrrp's status transition
  9. 杰奇python采集器_linux下能完美运行的杰奇采集器ckp
  10. 在spyder怎么升级_怎么更新spyder?
  11. css鼠标移入变成小手
  12. mqdf python_GitHub - jugg1024/court_recognition
  13. Packet Tracer - 排除 HSRP 故障
  14. 体验godaddy域名转入,添加A记录,及使用dnspod的NS
  15. Github标签管理
  16. 堆是先进先出,栈是先进后出
  17. 英语读书笔记-Book Lovers Day 04
  18. 用Python写前端是什么体验?
  19. Android kotlin 基础知识codelab Fragment Summary
  20. 《刻意练习》读书笔记思维导图精华版,探索成功的秘诀

热门文章

  1. Oracle 中的 Incarnation 到底是个什么?概念理解篇
  2. 一切美好源于启启宝盒
  3. antv x6 v2 使用笔记
  4. Git 忽略文件规则及设置
  5. WordPress5.4 解决修改固定链接404问题
  6. 如何安装eve和pnet(安装教程)
  7. Java大数据:大数据开发必须掌握的四种数据库
  8. 【Java面试】大厂裁员,小厂倒闭,如何搞定面试官Java SPI是什么?有什么用?
  9. 西恩科技冲刺深交所:半年营收10.7亿 赵志安部分股权被冻结
  10. 扬帆际海—跨境电商代运营靠谱吗?