首页 文章 精选 留言 我的

精选列表

搜索[android studio],共10000篇文章
优秀的个人博客,低调大师

Android开发小技巧之商品属性筛选与商品筛选

前言 一周一篇文章,果真是不太容易。顺便吐槽一下上周也就是9月5号的文章,几天之内就耗完了我1.4G的空间流量,吓得我都抽搐了。 这个次为大家带来的是一个完整的商品属性筛选与商品筛选。什么意思?都见过淘宝、京东等爱啪啪吧,里面有个商品详情,可以选择商品的属性,然后筛选出这个商品的具体型号,这样应该知道了吧?不知道也没关系,下面会有展示图。 [图片上传失败...(image-85d2ec-1534920518003)] 关于商品筛选是有两种方式(至少我只见到两种): 第一种: 将所有的商品的所有属性及详情返回给客户端,由客户端进行筛选。 淘宝用的就是这种。 第二种: 将所有的属性返回给客户端,客户选择完成属性后将属性发送给后台 ,再由后台根据属性筛选出具体商品返回给客户端。 京东就是这样搞的。。 两种方式各有各的好处: 第一种:体验性特别好,用户感觉不到延迟,立即选中立即就筛选出了详情。就是客户端比较费劲。。。 第二种:客户端比较省时间,但是体验性太差了,你想想,在网络不是很通畅的时候,你选择一个商品还得等老半天。 因为当时我没有参加到这个接口的设计,导致一直在变化。。我才不会告诉不是后台不给力,筛选不出来才一股脑的将所有锅甩给客户端。 技术点 流式布局 商品的属性并不是一样长的,所以需要自动适应内容的一个控件。 推荐hongyang的博客。我就是照着那个搞的。 RxJava 不要问我,我不知道,我也是新手,我就是用它做出了效果,至于有没有 用对,那我就不知道了。反正目的是达到了。 Json解析??? 准备 FlowLayout RxJava xml布局 这个部分的布局不是很难,只是代码量较多,咱们就省略吧,直接看效果吧 布局完成 可以看到机身颜色、内存、版本下面都是空的,因为我们还没有将属性筛选出来。 数据分析 先看看整体的数据结构是怎么样的 数据结构 每一个商品都有一个父类,仅作标识,不参与计算,比如数据中的华为P9就是一个商品的类目,在这下面有着各种属性组成的商品子类,这才是真正的商品。 而一个详细的商品是有三个基础属性所组成: 1. 版本 2. 内存 3. 制式 如上图中一个具体的商品的名称:"华为 P9全网通 3GB+32GB版 流光金 移动联通电信4G手机 双卡双待" 商品属性据结构 所以,要获得一个具体的商品是非常的简单,只需要客户选中的三个属性与上图中所对应的属性完全相同,就能得到这个商品。其中最关键的还是将所有的商品属性筛选出来。 筛选出所有属性及图片 本文中使用的数据是直接从Assets目录中直接读取的。 筛选出该商品的所有属性,怎么做呢?其实也是很简单的,直接for所有商品的所有属性,然后存储起来,去除重复的属性,那么最后剩下的就是该商品的属性了 /** * 初始化商品信息 * <li>1. 提取所有的属性</li> * <li>2. 提取所有颜色的照片</li> */ private void initGoodsInfo() { //所有的颜色 mColors = new ArrayList<>(); //筛选过程中临时存放颜色 mTempColors = new ArrayList<>(); //所有的内存 mMonerys = new ArrayList<>(); //筛选过程中临时的内存 mTempMonerys = new ArrayList<>(); //所有的版本 mVersions = new ArrayList<>(); //筛选过程中的临时版本 mTempVersions = new ArrayList<>(); //获取到所有的商品 shopLists = responseDto.getMsg().getChilds(); callBack.refreshSuccess("¥" + responseDto.getPricemin() + " - " + responseDto.getPricemax(), responseDto.getMsg().getParent().getName()); callBack.parentName(responseDto.getMsg().getParent().getName()); //遍历商品 Observable.from(shopLists) //转换对象 获取所有商品的属性集合 .flatMap(childsEntity -> Observable.from(childsEntity.getAttrinfo().getAttrs())) .subscribe(attrsEntity -> { //判断颜色 if (mActivity.getString(R.string.shop_color).equals(attrsEntity.getAttrname()) && !mTempColors.contains(attrsEntity.getAttrvalue())) { mColors.add(new TagInfo(attrsEntity.getAttrvalue())); mTempColors.add(attrsEntity.getAttrvalue()); } //判断制式 if (mActivity.getString(R.string.shop_standard).equals(attrsEntity.getAttrname()) && !mTempVersions.contains(attrsEntity.getAttrvalue())) { mVersions.add(new TagInfo(attrsEntity.getAttrvalue())); mTempVersions.add(attrsEntity.getAttrvalue()); } //判断内存 if (mActivity.getString(R.string.shop_monery).equals(attrsEntity.getAttrname()) && !mTempMonerys.contains(attrsEntity.getAttrvalue())) { mMonerys.add(new TagInfo(attrsEntity.getAttrvalue())); mTempMonerys.add(attrsEntity.getAttrvalue()); } }); // 提取出 每种颜色的照片 tempImageColor = new ArrayList<>(); mImages = new ArrayList<>(); //遍历所有的商品列表 Observable.from(shopLists) .subscribe(childsEntity -> { String color = childsEntity.getAttrinfo().getAttrs().get(0).getAttrvalue(); if (!tempImageColor.contains(color)) { mImages.add(childsEntity.getShowimg()); tempImageColor.add(color); } }); // 提取出 每种颜色的照片 //通知图片 callBack.changeData(mImages, "¥" + responseDto.getPricemin() + " - " + responseDto.getPricemax()); callBack.complete(null); } 初始化属性列表 属性之间是有一些关系的,比如我这里是以颜色为初始第一项,那么我就得根据颜色筛选出这个颜色下的所有内存,然后根据内存筛选出所有的版本。同时,只要颜色、内存、版本三个都选择了,就得筛选出这个商品。 {颜色>内存>版本}>具体商品 颜色 初始化颜色,设置选择监听,一旦用户选择了某个颜色,那么需要获取这个颜色下的所有内存,并且要开始尝试获取商品详情。 初始化颜色 /** * 初始化颜色 * * @hint */ private void initShopColor() { for (TagInfo mColor : mColors) { //初始化所有的选项为未选择状态 mColor.setSelect(false); } tvColor.setText("\"未选择颜色\""); mColors.get(colorPositon).setSelect(true); colorAdapter = new ProperyTagAdapter(mActivity, mColors); rlShopColor.setAdapter(colorAdapter); colorAdapter.notifyDataSetChanged(); rlShopColor.setTagCheckedMode(FlowTagLayout.FLOW_TAG_CHECKED_SINGLE); rlShopColor.setOnTagSelectListener((parent, selectedList) -> { colorPositon = selectedList.get(0); strColor = mColors.get(colorPositon).getText(); // L.e("选中颜色:" + strColor); tvColor.setText("\"" + strColor + "\""); //获取颜色照片 initColorShop(); //查询商品详情 iterationShop(); }); } 获取颜色下所有的内存和该颜色的照片 /** * 初始化相应的颜色的商品 获得 图片 */ private void initColorShop() { //初始化 选项数据 Observable.from(mMonerys).subscribe(tagInfo -> { tagInfo.setChecked(true); }); L.e("开始筛选颜色下的内存----------------------------------------------------------------------------------"); final List<String> tempColorMemery = new ArrayList<>(); //筛选内存 Observable.from(shopLists) .filter(childsEntity -> childsEntity.getAttrinfo().getAttrs().get(0).getAttrvalue().equals(strColor)) .flatMap(childsEntity -> Observable.from(childsEntity.getAttrinfo().getAttrs())) .filter(attrsEntity -> mActivity.getString(R.string.shop_monery).equals(attrsEntity.getAttrname())) .subscribe(attrsEntity -> { tempColorMemery.add(attrsEntity.getAttrvalue()); // L.e("内存:"+attrsEntity.getAttrvalue()); }); Observable.from(mTempMonerys) .filter(s -> !tempColorMemery.contains(s)) .subscribe(s -> { L.e("没有的内存:" + s); mMonerys.get(mTempMonerys.indexOf(s)).setChecked(false); }); momeryAdapter.notifyDataSetChanged(); L.e("筛选颜色下的内存完成----------------------------------------------------------------------------------"); //获取颜色的照片 ImageHelper.loadImageFromGlide(mActivity, mImages.get(tempImageColor.indexOf(strColor)), ivShopPhoto); } 根据选中的属性查询是否存在该商品 /** * 迭代 选择商品属性 */ private void iterationShop() { // 选择的内存 选择的版本 选择的颜色 if (strMemory == null || strVersion == null || strColor == null) return; //隐藏购买按钮 显示为缺货 resetBuyButton(false); Observable.from(shopLists) .filter(childsEntity -> childsEntity.getAttrinfo().getAttrs().get(0).getAttrvalue().equals(strColor)) .filter(childsEntity -> childsEntity.getAttrinfo().getAttrs().get(1).getAttrvalue().equals(strVersion)) .filter(childsEntity -> childsEntity.getAttrinfo().getAttrs().get(2).getAttrvalue().equals(strMemory)) .subscribe(childsEntity -> { L.e(childsEntity.getShopprice()); tvPrice.setText("¥" + childsEntity.getShopprice()); // ImageHelper.loadImageFromGlide(mActivity, Constant.IMAGE_URL + childsEntity.getShowimg(), ivShopPhoto); L.e("已找到商品:" + childsEntity.getName() + " id:" + childsEntity.getPid()); selectGoods = childsEntity; tvShopName.setText(childsEntity.getName()); //显示购买按钮 resetBuyButton(true); initShopStagesCount++; }); } 内存 通过前面一步,已经获取了所有的内存。这一步只需要展示该所有内存,设置选择监听,选择了某个内存后就根据 选择颜色>选择内存 获取所有的版本。并在在其中也是要iterationShop()查询商品的,万一你是往回点的时候呢? 初始化版本 /** * 初始化内存 */ private void initShopMomery() { for (TagInfo mMonery : mMonerys) { mMonery.setSelect(false); Log.e(" ", "initShopMomery: " + mMonery.getText()); } tvMomey.setText("\"未选择内存\""); mMonerys.get(momeryPositon).setSelect(true); //-----------------------------创建适配器 momeryAdapter = new ProperyTagAdapter(mActivity, mMonerys); rlShopMomery.setAdapter(momeryAdapter); rlShopMomery.setTagCheckedMode(FlowTagLayout.FLOW_TAG_CHECKED_SINGLE); rlShopMomery.setOnTagSelectListener((parent, selectedList) -> { momeryPositon = selectedList.get(0); strMemory = mMonerys.get(momeryPositon).getText(); // L.e("选中内存:" + strMemory); iterationShop(); tvMomey.setText("\"" + strMemory + "\""); iterationVersion(); }); } 根据已选择的颜色和内存获取到版本 /** * 迭代 获取版本信息 */ private void iterationVersion() { if (strColor == null || strMemory == null) { return; } // L.e("开始迭代版本"); Observable.from(mVersions).subscribe(tagInfo -> { tagInfo.setChecked(true); }); final List<String> iterationTempVersion = new ArrayList<>(); //1. 遍历出 这个颜色下的所有手机 //2. 遍历出 这些手机的所有版本 Observable.from(shopLists) .filter(childsEntity -> childsEntity.getAttrinfo().getAttrs().get(0).getAttrvalue().equals(strColor)) .filter(childsEntity -> childsEntity.getAttrinfo().getAttrs().get(2).getAttrvalue().equals(strMemory)) .flatMap(childsEntity -> Observable.from(childsEntity.getAttrinfo().getAttrs())) .filter(attrsEntity -> attrsEntity.getAttrname().equals(mActivity.getString(R.string.shop_standard))) .subscribe(attrsEntity -> { iterationTempVersion.add(attrsEntity.getAttrvalue()); }); Observable.from(mTempVersions).filter(s -> !iterationTempVersion.contains(s)).subscribe(s -> { mVersions.get(mTempVersions.indexOf(s)).setChecked(false); }); versionAdapter.notifyDataSetChanged(); // L.e("迭代版本完成"); } 版本 其实到了这一步,已经算是完成了,只需要设置监听,获取选中的版本,然后开始查询商品。 /** * 初始化版本 */ private void initShopVersion() { for (TagInfo mVersion : mVersions) { mVersion.setSelect(false); } tvVersion.setText("\"未选择版本\""); mVersions.get(versionPositon).setSelect(true); //-----------------------------创建适配器 versionAdapter = new ProperyTagAdapter(mActivity, mVersions); rlShopVersion.setAdapter(versionAdapter); rlShopVersion.setTagCheckedMode(FlowTagLayout.FLOW_TAG_CHECKED_SINGLE); rlShopVersion.setOnTagSelectListener((parent, selectedList) -> { versionPositon = selectedList.get(0); strVersion = mVersions.get(versionPositon).getText(); // L.e("选中版本:" + strVersion); iterationShop(); tvVersion.setText("\"" + strVersion + "\""); }); } 完成 最终效果图如下: [图片上传失败...(image-6c839e-1534920518003)] 不要在意后面的轮播图,那其实很简单的。 最后 文采不是很好啊。不是很完美的表达出我自己的想法 如果有可能,能给我star吗? 源码地址

优秀的个人博客,低调大师

Android初级进阶之自定义果冻视图(BouncingJellyView)(一)

前言 上一个周没有写博客,是我太懒,无法坚持。在上一个周,除去工作的任务(迭代版本,修复BUG)之外,我一直在模仿一个UI效果。我呢,算是一个米粉,我前面的博客,有一些效果就是来自MIUI。在MIUI中,很多的列表都具有弹性和粘性,个人觉得这个效果不错,于是就模仿了一下。 本来开始之初是为了将这个效果封装成为一个单独的UI控件,结果写着写着就发现这样是不合理的,于是就放在一旁等待解决方案,先看看实际的效果吧。(前面两个是我项目中的实际效果) 订单详情效果 设置列表效果 demo->RecyclerView效果 怎么样,看上去效果还是挺可以的吧,不得不说,MIUI在一些小细节上面做得非常不错,很多效果都值得我们深入的进行学习。 注意 本博客最主要的是为了向大家展示一种解决思路,文章中的类表效果用到实际项目中还是有些许问题。 为了方便起见,本文中使用的动画效果来自于JakeWharton大神的NineOldAndroids的支持库,个人非常不建议新手直接就来使用开源库,最起码应该熟悉一些基础。 分析 刚开始的时候,我一直在网上找类似的效果,一直是没有找到。直到我学习完属性动画之后才发现,其实这个效果实现起来是非常的简单。 整个效果看上去分为上拉和下来,上拉和下拉的时候进行缩放。 1. 下拉: 将View的中心点移到(width/2,0)中进行Scale缩放 2. 上拉 将View的中心点移到(width/2,height)中进行Scale缩放 松手之后会有一个回弹效果,使用ValueAnimator来进行散发scale值,采用OvershootInterpolator差值器就能达到这样的效果。 编码 1. 选择继承 自定义View又几种方式: 继承自View实现效果。 继承原生控件进行拓展。 组合控件。 很明显,效果图中都是包含了子控件的,可以选择继承ViewGroup来实现,但是我根本不关心子控件的一些测量和layout,所以需要继承已经实现的ViewGroup。最后我选定的是使用ScrollView,原因是为了兼容滚动,并且需要监听是否已经滚动到了底部。 2.准备工作 创建项目 引用开源库 compile 'com.nineoldandroids:library:2.4.0' 创建自定义控件类继承ScrollView,实现三个构造方法,并且在xml中引用 3. 自定义属性 首先思考我们需要哪一些属性,比方说手指抬起后回弹的速度,回弹的效果方式(其实就是不同的差值器),能够进行果冻缩放的方式,只能是顶部、底部或者不限制。 在value文件夹中创建attr.xml <attr name="BouncingDuration" format="integer" /> <attr name="BouncingInterpolator" format="enum"> <enum name="OvershootInterpolator" value="1" /> <enum name="BounceInterpolator" value="2" /> <enum name="LinearInterpolator" value="3" /> <enum name="AccelerateDecelerateInterpolator" value="4" /> </attr> <attr name="BouncingType" format="enum"> <enum name="none" value="0" /> <enum name="top" value="1" /> <enum name="bottom" value="2" /> <enum name="both" value="3" /> </attr> <declare-styleable name="BouncingJellyScrollView"> <attr name="BouncingDuration" /> <attr name="BouncingInterpolator" /> <attr name="BouncingType" /> </declare-styleable> 将attr独立出来的原因是我还有几个控件需要使用相同的一些属性。 5. 初始化 在构造方法中初始化一些常量值和属性。 其它的一些工具类方法 public class BouncingType { public static final int NONE = 0; public static final int TOP = 1; public static final int BOTTOM = 2; public static final int BOTH = 3; } public class BouncingInterpolatorType { public static final int OVERSHOOT_INTERPOLATOR = 1; public static final int BOUNCE_INTERPOLATOR = 2; public static final int LINEAR_INTERPOLATOR = 3; public static final int ACCELERATE_DECELERATE_INTERPOLATOR = 4; /** * 获取弹跳类型 * * @return */ public static TimeInterpolator getTimeInterpolator(int type) { TimeInterpolator mTimeInterpolator = null; switch (type) { case OVERSHOOT_INTERPOLATOR: mTimeInterpolator = new OvershootInterpolator(); break; case BOUNCE_INTERPOLATOR: mTimeInterpolator = new BounceInterpolator(); break; case LINEAR_INTERPOLATOR: mTimeInterpolator = new LinearInterpolator(); break; case ACCELERATE_DECELERATE_INTERPOLATOR: mTimeInterpolator = new AccelerateDecelerateInterpolator(); break; } return mTimeInterpolator; } } 初始化属性 /** * @param attrs */ private void initAttr(AttributeSet attrs) { if (attrs != null) { TypedArray typedArray = getContext().obtainStyledAttributes(attrs, R.styleable.BouncingJellyScrollView); //差值器 mTimeInterpolator = BouncingInterpolatorType.getTimeInterpolator(typedArray.getInteger( R.styleable.BouncingJellyScrollView_BouncingInterpolator , BouncingInterpolatorType.OVERSHOOT_INTERPOLATOR)); //回弹速度 mBouncingDuration = typedArray.getInteger(R.styleable.BouncingJellyScrollView_BouncingDuration, mBouncingDuration); //果冻类型 mBouncingType = typedArray.getInt(R.styleable.BouncingJellyScrollView_BouncingType, BouncingType.BOTH); typedArray.recycle(); //获取是差值 整个屏幕的三倍大小 bouncingOffset=ScreenUtils.getScreenHeight(getContext()) * 3; } } onSizeChanged中验证模式 @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); //判断可滚动的内容是不是小于整个屏幕的高度,以防底部进行所动 int contentHeight = getChildAt(0).getHeight(); if (contentHeight > 0 && contentHeight <= ScreenUtils.getScreenHeight(getContext())) { mBouncingType = BouncingType.TOP; } } 4. 开始编码 因为我们继承的是ViewGroup,子View还需要拦截事件,所以我们需要重写dispatchTouchEvent方法,并且在其中拦截事件分发和做缩放效果。 先实现在顶部进行滑动的时候随着手指移动而进行缩放 /** * 从顶部开始滑动 */ public void bouncingTo() { //设置X坐标点 ViewHelper.setPivotX(this, getWidth() / 2); //设置Y坐标点 ViewHelper.setPivotY(this, 0); //进行缩放 ViewHelper.setScaleY(this, 1.0f + offsetScale); } /** * 从顶部开始滑动 */ public void bouncingBottom() { //设置X坐标点 ViewHelper.setPivotX(this, getWidth() / 2); //设置Y坐标点 ViewHelper.setPivotY(this, getHeight()); ViewHelper.setScaleY(this, 1.0f + offsetScale); } 在ACTION_DOWN记录按下的坐标,用于计算缩放值和进行回弹。因为ACTION_DOWN事件必定会传递到子view的,所以不能直接返回true。 //移动坐标 dowY = (int) event.getRawY(); //按下坐标 用于计算缩放值 dowY2 = (int) event.getRawY(); 在ACTION_MOVE中进行事件分发和缩放。 实现顶部滑动缩放,主要原理是判断当前是不是滚动到了顶部,获取手指移动的方向和距离。 moveX = (int) event.getRawX(); moveY = (int) event.getRawY(); //dy值 判断方向 int dy = moveY - dowY; dowY = moveY; //顶部 if (dy > 0 && getScrollY() == 0) { //判断果冻的类型 if (mBouncingType == BouncingType.TOP || mBouncingType == BouncingType.BOTH) { //获取现在坐标与按下坐标的差值 int abs = moveY - dowY2; //计算缩放值 offsetScale = (Math.abs(abs) / bouncingOffset); if (offsetScale > 0.3f) { offsetScale = 0.3f; } isTop = true; bouncingTo(); return true; } } 实现第一步效果如下: 顶部下拉缩放1 回拉恢复 效果是出来了,从顶部下拉的时候慢慢的缩放了,但是如果在下拉一定距离后上拉会是怎么样的呢?应该是慢慢的缩回去,然后再进行滚动。 需要在顶部if后面再加上判断 if (getScrollY() == 0 && dy < 0 && offsetScale > 0) {//为顶部 并且dy为下拉 并且缩放值大于0 if (mBouncingType == BouncingType.TOP || mBouncingType == BouncingType.BOTH) { //获取现在坐标与按下坐标的差值 int abs = moveY - dowY2; //计算缩放值 offsetScale = (Math.abs(abs) / bouncingOffset); if (offsetScale > 0.3f) { offsetScale = 0.3f; } if (abs <= 0) { offsetScale = 0; dowY2 = moveY; } isTop = true; bouncingTo(); return true; } } 效果如下: 顶部下拉缩放回拉恢复 手指抬起进行回弹 前面两步完成了整个拉取的过程,现在只要加上手机抬起的时候进行回弹就可以了。整个回弹过程是有一个时间段,并且还有一个效果。采用ValueAnimator来散发offsetScale值来不断的改变缩放值就能达到效果。 ACTION_UP代码 if (mBouncingType != BouncingType.NONE) { if (offsetScale > 0) { backBouncing(offsetScale, 0); return true; } } /** * 进行回弹 * * @param from * @param to */ private void backBouncing(final float from, final float to) { //初始化 if (animator != null && animator.isRunning()) { animator.cancel(); animator = null; offsetScale = 0; bouncingTo(); } if (mTimeInterpolator == null) { mTimeInterpolator = new OvershootInterpolator(); } //散发值 animator = ValueAnimator.ofFloat(from, to).setDuration(mBouncingDuration); animator.setInterpolator(mTimeInterpolator);//差值器 animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { //获取动画阶段的值 offsetScale = (float) animation.getAnimatedValue(); if (isTop) {//回弹到顶部 bouncingTo(); } else {//回弹到底部 bouncingBottom(); } } }); animator.start(); } 效果如下: 顶部下拉缩放抬起回弹 其实到这里,整个果冻视图就已经算是完成了,至于底部滑动,缩放都是一样的,只是方向,值相反而已。判断是否已经滚动到了底部,判断方向等。以下附上dispatchTouchEvent的代码,代码量有些冗余,只是为了每个部分的清晰而已。 @Override public boolean dispatchTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: //移动坐标 dowY = (int) event.getRawY(); //按下坐标 用于计算缩放值 dowY2 = (int) event.getRawY(); break; case MotionEvent.ACTION_MOVE: if (mBouncingType != BouncingType.NONE) { moveX = (int) event.getRawX(); moveY = (int) event.getRawY(); //dy值 判断方向 int dy = moveY - dowY; dowY = moveY; //顶部 if (dy > 0 && getScrollY() == 0) { //判断果冻的类型 if (mBouncingType == BouncingType.TOP || mBouncingType == BouncingType.BOTH) { //获取现在坐标与按下坐标的差值 int abs = moveY - dowY2; //计算缩放值 offsetScale = (Math.abs(abs) / bouncingOffset); if (offsetScale > 0.3f) { offsetScale = 0.3f; } isTop = true; bouncingTo(); return true; } } else if (getScrollY() == 0 && dy < 0 && offsetScale > 0) {//为顶部 并且dy为下拉 并且缩放值大于0 if (mBouncingType == BouncingType.TOP || mBouncingType == BouncingType.BOTH) { //获取现在坐标与按下坐标的差值 int abs = moveY - dowY2; //计算缩放值 offsetScale = (Math.abs(abs) / bouncingOffset); if (offsetScale > 0.3f) { offsetScale = 0.3f; } if (abs <= 0) { offsetScale = 0; dowY2 = moveY; } isTop = true; bouncingTo(); return true; } } //底部 if (dy < 0 && getScrollY() + getHeight() >= computeVerticalScrollRange()) {//滚动到底部 if (mBouncingType == BouncingType.BOTTOM || mBouncingType == BouncingType.BOTH) { int abs = moveY - dowY2; offsetScale = (Math.abs(abs) / bouncingOffset); if (offsetScale > 0.3f) { offsetScale = 0.3f; } isTop = false; bouncingBottom(); } } else if (dy > 0 && getScrollY() + getHeight() >= computeVerticalScrollRange() && offsetScale > 0) { if (mBouncingType == BouncingType.BOTTOM || mBouncingType == BouncingType.BOTH) { int abs = moveY - dowY2; offsetScale = (Math.abs(abs) / bouncingOffset); if (offsetScale > 0.3f) { offsetScale = 0.3f; } if (abs >= 0) { offsetScale = 0; dowY2 = moveY; } isTop = false; bouncingBottom(); return true; } } } break; case MotionEvent.ACTION_UP: if (mBouncingType != BouncingType.NONE) { if (offsetScale > 0) { backBouncing(offsetScale, 0); return true; } } break; } return super.dispatchTouchEvent(event); } 来一个整体完成的效果图: 顶部下拉缩放完整 其它View RecyclerView,ListVIew实现的原理都是一样的,判断是否在顶部,滑动方向等,再进行缩放即可。另外我实现了一个RecycerView的demo,代码和上面的基本上一致。 最后 源码地址 最后,这暂时是一个最基本的,等有时间我会继续完成这个自定义View的。如果可能,希望大家能够给我一个star。

资源下载

更多资源
Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。

用户登录
用户注册