热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Android框架之路——Glide加载图片(结合RecyclerView、CardView)

Android框架之路——Glide加载图片一、简介:在泰国举行的谷歌开发者论坛上,谷歌为我们介绍了一个名叫Glide的图片加载库,作者

Android框架之路——Glide加载图片


一、简介:


在泰国举行的谷歌开发者论坛上,谷歌为我们介绍了一个名叫 Glide 的图片加载库,作者是bumptech。这个库被广泛的运用在google的开源项目中,包括2014年google I/O大会上发布的官方app。

  • 使用简单
  • 可配置度高,自适应程度高
  • 支持常见图片格式 Jpg png gif webp
  • 支持多种数据源 网络、本地、资源、Assets 等
  • 高效缓存策略 支持Memory和Disk图片缓存 默认Bitmap格式采用RGB_565内存使用至少减少一半
  • 生命周期集成 根据Activity/Fragment生命周期自动管理请求
  • 高效处理Bitmap 使用Bitmap Pool使Bitmap复用,主动调用recycle回收需要回收的Bitmap,减小系统回收压力.

然后呢,这篇文章的重点不完全放在了Glide上,重要的是完成了一个demo——RecyclerView+CardView+Glide加载图片实现瀑布流,写过瀑布流的都知道,这中间有个bug,滑动的时候item位置会变动,这效果就很难受了。本文参考了一位前辈的方法,将其移植到我们的demo中来,算是基本解决了这个问题。


二、添加依赖:

去github上查看最新添加依赖:https://github.com/bumptech/glide

repositories {mavenCentral() // jcenter() works as well because it pulls from Maven Central
}dependencies {compile 'com.github.bumptech.glide:glide:3.7.0'compile 'com.android.support:support-v4:19.1.0'
}

如果你需要各种变换效果,你可以继续添加:

compile 'jp.wasabeef:glide-transformations:2.0.1'
compile 'jp.co.cyberagent.android.gpuimage:gpuimage-library:1.3.0'

三、解锁姿势:


  1. 最简单的使用:

    Glide.with(this).load("http://inthecheesefactory.com/uploads/source/nestedfragment/fragments.png").into(imageView);

  2. with()的参数:

    • with(Context context). 使用Application上下文,Glide请求将不受Activity/Fragment生命周期控制。
    • with(Activity activity). 使用Activity作为上下文,Glide的请求会受到Activity生命周期控制。
    • with(FragmentActivity activity). Glide的请求会受到FragmentActivity生命周期控制。
    • with(android.app.Fragment fragment). Glide的请求会受到Fragment 生命周期控制。
    • with(android.support.v4.app.Fragment fragment). Glide的请求会受到Fragment生命周期控制。
  3. load()的使用:
    Glide基本可以load任何可以拿到的媒体资源,load的参数也不局限于String类型。
    可以拿到的资源:

    • SD卡资源:load(“file://”+ Environment.getExternalStorageDirectory().getPath()+”/test.jpg”)
    • assets资源:load(“file:///android_asset/f003.gif”)
    • raw资源:load(“Android.resource://com.frank.glide/raw/raw_1”)或load(“android.resource://com.frank.glide/raw/”+R.raw.raw_1)
    • drawable资源:load(“android.resource://com.frank.glide/drawable/news”)或load(“android.resource://com.frank.glide/drawable/”+R.drawable.news)
    • ContentProvider资源:load(“content://media/external/images/media/139469”)
    • http资源:load(“http://img.my.csdn.NET/uploads/201508/05/1438760757_3588.jpg“)
    • https资源:load(“https://img.alicdn.com/tps/TB1uyhoMpXXXXcLXVXXXXXXXXXX-476-538.jpg_240x5000q50.jpg_.webp“)

    load()的参数类型:

    • load(Uri uri),
    • load(File file),
    • load(Integer resourceId),
    • load(URL url),
    • load(byte[] model),
    • load(T model),
    • loadFromMediaStore(Uri uri)
  4. 重要功能:
    • .skipMemoryCache(true) 禁止内存缓存
    • .get(context).clearMemory() 清除内存缓存,必须在UI线程中调用
    • .diskCacheStrategy(DiskCacheStrategy.NONE) 禁止磁盘缓存
    • .get(applicationContext).clearDiskCache() 清除磁盘缓存,必须在后台线程中调用,建议同时clearMemory()
    • new GetDiskCacheSizeTask(textView).execute(new File(getCacheDir(),DiskCache.Factory.DEFAULT_DISK_CACHE_DIR)) 获取缓存大小
    • .priority(Priority.HIGH/LOW) 指定资源的优先加载顺序
    • .thumbnail(0.1f) 先显示缩略图,再显示原图
    • 对图片进行裁剪、模糊、滤镜等处理
    • 对请求状态进行监听
    • 对资源的下载进度进行监听
  5. Api方法说明:
    • thumbnail(float sizeMultiplier)——请求给定系数的缩略图。如果缩略图比全尺寸图先加载完,就显示缩略图,否则就不显示。系数sizeMultiplier必须在(0,1)之间,可以递归调用该方法。
    • sizeMultiplier(float sizeMultiplier)——在加载资源之前给Target大小设置系数。
    • diskCacheStrategy(DiskCacheStrategy strategy)——设置缓存策略。默认采用DiskCacheStrategy.RESULT策略,对于download only操作要使用DiskCacheStrategy.SOURCE。
      • DiskCacheStrategy.SOURCE:缓存原始数据,
      • DiskCacheStrategy.RESULT:缓存变换(如缩放、裁剪等)后的资源数据,
      • DiskCacheStrategy.NONE:什么都不缓存,
      • DiskCacheStrategy.ALL:缓存SOURC和RESULT。
    • priority(Priority priority)——指定加载的优先级,优先级越高越优先加载,但不保证所有图片都按序加载。枚举Priority.IMMEDIATE,Priority.HIGH,Priority.NORMAL,Priority.LOW。默认为Priority.NORMAL。
    • dontAnimate()——移除所有的动画。
    • animate(int animationId)——在异步加载资源完成时会执行该动画。
    • animate(ViewPropertyAnimation.Animator animator)——在异步加载资源完成时会执行该动画。
    • placeholder(int resourceId)——设置资源加载过程中的占位Drawable。
    • placeholder(Drawable drawable)——设置资源加载过程中的占位Drawable。
    • fallback(int resourceId)——设置model为空时要显示的Drawable。如果没设置fallback,model为空时将显示error的Drawable,如果error的Drawable也没设置,就显示placeholder的Drawable。
    • fallback(Drawable drawable)——设置model为空时显示的Drawable。
    • error(int resourceId)——设置load失败时显示的Drawable。
    • error(Drawable drawable)——设置load失败时显示的Drawable。
    • listener(RequestListener

四、来个Demo:


  1. 这里我们会用到ButterKnife,不会使用的可以看一下Android框架之路——ButterKnife的使用;
  2. 看一下Demo效果:

    "加载资源图片");Glide.with(this).load(R.mipmap.ic_launcher).into(mIvGlide2);mTvGlide3.setText("加载手机SD卡图片");String path = Environment.getExternalStorageDirectory() + "/back.jpg";File file = new File(path);Uri uri = Uri.fromFile(file);Glide.with(this).load(uri).into(mIvGlide3);mTvGlide4.setText("加载网络gif");String gifUrl = "http://b.hiphotos.baidu.com/zhidao/pic/item/faedab64034f78f066abccc57b310a55b3191c67.jpg";Glide.with(this).load(gifUrl).into(mIvGlide4);mTvGlide5.setText("加载资源gif");Glide.with(this).load(R.drawable.loading).into(mIvGlide5);mTvGlide6.setText("加载本地gif");String path1 = Environment.getExternalStorageDirectory() + "/meinv2.jpg";File file1 = new File(path1);Uri uri1 = Uri.fromFile(file1);Glide.with(this).load(uri1).placeholder(R.mipmap.ic_launcher).into(mIvGlide6);mTvGlide7.setText("加载本地小视频和快照");String path2 = Environment.getExternalStorageDirectory() + "/video.mp4";File file2 = new File(path2);Uri uri2 = Uri.fromFile(file2);Glide.with(this).load(uri2).placeholder(R.mipmap.ic_launcher).into(mIvGlide7);mTvGlide8.setText("设置缩略图比例,然后,先加载缩略图,再加载原图");Glide.with(this).load("http://7xi8d6.com1.z0.glb.clouddn.com/2017-04-28-18094719_120129648541065_8356500748640452608_n.jpg").thumbnail(0.1f).centerCrop().placeholder(R.mipmap.ic_launcher).into(mIvGlide8);mTvGlide9.setText("先建立一个缩略图对象,然后,先加载缩略图,再加载原图");DrawableRequestBuilder builder = Glide.with(this).load("http://7xi8d6.com1.z0.glb.clouddn.com/2017-04-28-18094719_120129648541065_8356500748640452608_n.jpg");Glide.with(this).load(uri2).thumbnail(builder).centerCrop().placeholder(R.mipmap.ic_launcher).into(mIvGlide9);}

  3. Glide的各种变换效果:
    效果都在注释中,你可以看效果来进行选择哪种变换。有些我也叫不出是啥,尴尬….还是看demo效果吧。

    @Override
    public void onBindViewHolder(TransViewHolder holder, int position) {int integer = Integer.parseInt(mList.get(position));holder.mTvTran.setText("样式"+(position+1));switch (integer) {//五角星形的外框Maskcase 1: {int width = UIUtils.dip2px(mContext, 133.33f);int height = UIUtils.dip2px(mContext, 126.33f);Glide.with(mContext).load(R.drawable.demo).override(width, height).bitmapTransform(new CenterCrop(mContext),new MaskTransformation(mContext, R.drawable.mask_starfish)).into(holder.mIvTran);break;}//点9图片的外框Maskcase 2: {int width = UIUtils.dip2px(mContext, 150.0f);int height = UIUtils.dip2px(mContext, 100.0f);Glide.with(mContext).load(R.drawable.demo).override(width, height).bitmapTransform(new CenterCrop(mContext),new MaskTransformation(mContext, R.drawable.mask_chat_right)).into(holder.mIvTran);break;}//裁剪图片的上方部分区域case 3:Glide.with(mContext).load(R.drawable.demo).bitmapTransform(new CropTransformation(mContext, 300, 100, CropTransformation.CropType.TOP)).into(holder.mIvTran);break;//裁剪图片的上方100-300区域case 4:Glide.with(mContext).load(R.drawable.demo).bitmapTransform(new CropTransformation(mContext, 300, 100)).into(holder.mIvTran);break;//裁剪图片的下方部分区域case 5:Glide.with(mContext).load(R.drawable.demo).bitmapTransform(new CropTransformation(mContext, 300, 100, CropTransformation.CropType.BOTTOM)).into(holder.mIvTran);break;//显示方形图case 6:Glide.with(mContext).load(R.drawable.demo).bitmapTransform(new CropSquareTransformation(mContext)).into(holder.mIvTran);break;//显示圆形图case 7:Glide.with(mContext).load(R.drawable.demo).bitmapTransform(new CropCircleTransformation(mContext)).into(holder.mIvTran);break;//彩色滤镜样式case 8:Glide.with(mContext).load(R.drawable.demo).bitmapTransform(new ColorFilterTransformation(mContext, Color.argb(80, 255, 0, 0))).into(holder.mIvTran);break;//灰度图case 9:Glide.with(mContext).load(R.drawable.demo).bitmapTransform(new GrayscaleTransformation(mContext)).into(holder.mIvTran);break;//边框圆角化图片case 10:Glide.with(mContext).load(R.drawable.demo).bitmapTransform(new RoundedCornersTransformation(mContext, 30, 0,RoundedCornersTransformation.CornerType.BOTTOM)).into(holder.mIvTran);break;//毛玻璃效果case 11:Glide.with(mContext).load(R.drawable.check).bitmapTransform(new BlurTransformation(mContext, 25)).into(holder.mIvTran);break;case 12:Glide.with(mContext).load(R.drawable.demo).bitmapTransform(new ToonFilterTransformation(mContext)).into(holder.mIvTran);break;case 13:Glide.with(mContext).load(R.drawable.check).bitmapTransform(new SepiaFilterTransformation(mContext)).into(holder.mIvTran);break;case 14:Glide.with(mContext).load(R.drawable.check).bitmapTransform(new ContrastFilterTransformation(mContext, 2.0f)).into(holder.mIvTran);break;case 15:Glide.with(mContext).load(R.drawable.check).bitmapTransform(new InvertFilterTransformation(mContext)).into(holder.mIvTran);break;case 16:Glide.with(mContext).load(R.drawable.check).bitmapTransform(new PixelationFilterTransformation(mContext, 20)).into(holder.mIvTran);break;case 17:Glide.with(mContext).load(R.drawable.check).bitmapTransform(new SketchFilterTransformation(mContext)).into(holder.mIvTran);break;case 18:Glide.with(mContext).load(R.drawable.check).bitmapTransform(new SwirlFilterTransformation(mContext, 0.5f, 1.0f, new PointF(0.5f, 0.5f))).into(holder.mIvTran);break;case 19:Glide.with(mContext).load(R.drawable.check).bitmapTransform(new BrightnessFilterTransformation(mContext, 0.5f)).into(holder.mIvTran);break;case 20:Glide.with(mContext).load(R.drawable.check).bitmapTransform(new KuwaharaFilterTransformation(mContext, 25)).into(holder.mIvTran);break;case 21:Glide.with(mContext).load(R.drawable.check).bitmapTransform(new VignetteFilterTransformation(mContext, new PointF(0.5f, 0.5f),new float[] { 0.0f, 0.0f, 0.0f }, 0f, 0.75f)).into(holder.mIvTran);break;}
    }

  4. Glide的小综合案列(RecyclerView+CardView加载美女图片):

    • 使用的api:Gankio的福利——http://gank.io/api/data/福利/10/1,10代表每页加载数量,1代表加载第几页数据。
    • 使用CardView:

      app:cardBackgroundColor //这是设置背景颜色
      app:cardCornerRadius //这是设置圆角大小
      app:cardElevation //这是设置z轴的阴影
      app:cardMaxElevation //这是设置z轴的最大高度值
      app:cardUseCompatPadding //是否使用CompatPadding
      app:cardPreventCornerOverlap //是否使用PreventCornerOverlap
      app:contentPadding // 设置内容的padding
      app:contentPaddingLeft //设置内容的左padding
      app:contentPaddingTop //设置内容的上padding
      app:contentPaddingRight //设置内容的右padding
      app:contentPaddingBottom //设置内容的底padding

      我的item_mm.xml如下,其中CardView的高度要设置为wrap_content,ImageView的高度也要设置为wrap_content。别问我为什么,我也不准备回答这个问题,因为我也不知道,你可以设置成别的试试就知道了。


      <android.support.v7.widget.CardView
      xmlns:android&#61;"http://schemas.android.com/apk/res/android"xmlns:app&#61;"http://schemas.android.com/apk/res-auto"app:cardCornerRadius&#61;"3dp"app:cardElevation&#61;"3dp"android:layout_width&#61;"wrap_content"android:layout_height&#61;"wrap_content"android:orientation&#61;"vertical">
      <ImageView
      android:id&#61;"&#64;&#43;id/iv_mm"android:layout_width&#61;"wrap_content"android:layout_height&#61;"wrap_content"android:scaleType&#61;"centerCrop"/>
      android.support.v7.widget.CardView>

    • 配置权限&#xff1a;

      <uses-permission android:name&#61;"android.permission.INTERNET"/>
      <uses-permission android:name&#61;"android.permission.WRITE_EXTERNAL_STORAGE" />

    • 编写Addapter类&#xff0c;在onBindViewHolder中使用Glide通过URL加载图片即可&#xff1a;

      public class MeiziAdapter extends RecyclerView.Adapter<MeiziAdapter.ViewHolder>{private Context mContext;private List mData;public MeiziAdapter(Context context, List data) {mContext &#61; context;mData &#61; data;}&#64;Overridepublic ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {View view &#61; LayoutInflater.from(mContext).inflate(R.layout.item_meizi, parent, false);return new ViewHolder(view);}&#64;Overridepublic void onBindViewHolder(ViewHolder holder, int position) {String url &#61; mData.get(position).getResults().get(0).getUrl();Glide.with(mContext).load(url).into(holder.mIvMm);}&#64;Overridepublic int getItemCount() {if(mData &#61;&#61; null)return 0;return mData.size();}public class ViewHolder extends RecyclerView.ViewHolder {&#64;BindView(R.id.iv_mm)ImageView mIvMm;ViewHolder(View view) {super(view);ButterKnife.bind(this, view);}}
      }

    • 问题&#xff1a;现在我们已经可以实现加载图片的效果了&#xff0c;但是在上下滑动时&#xff0c;出现重新定义宽高&#xff0c;导致cardview滑动状态。这篇文章也提到这件事。我参考一个开源的软件中的写法&#xff0c;附在下面&#xff1a;

      • 在Adapter中重写getItemViewType方法&#xff1b;

        &#64;Override
        public int getItemViewType(int position) {WindowManager windowManager &#61; (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);DisplayMetrics dm &#61; new DisplayMetrics();Display display &#61; windowManager.getDefaultDisplay();display.getMetrics(dm);final int screenWidth &#61; dm.widthPixels;return Math.round((float) screenWidth / (float) mData.get(position).getHeight() * 10f);
        }

      • 对我们的Meizi.java添加一个属性——高度height&#xff0c;并生成getter、setter方法&#xff1b;

        ......
        private int height;public int getHeight() {return height;
        }public void setHeight(int height) {this.height &#61; height;
        }public boolean isError() {return error;
        }
        .......

      • 下面着重是我们的onBindViewHolder方法&#xff0c;主体思想还是根据宽度获取该显示的高度。

        &#64;Override
        public void onBindViewHolder(final ViewHolder holder, final int position) {//存在记录的高度时先Layout再异步加载图片if (mData.get(holder.getAdapterPosition()).getHeight() > 0) {ViewGroup.LayoutParams layoutParams &#61; holder.mIvMm.getLayoutParams();layoutParams.height &#61; mData.get(holder.getAdapterPosition()).getHeight();}//获取屏幕宽度WindowManager windowManager &#61; (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);DisplayMetrics dm &#61; new DisplayMetrics();Display display &#61; windowManager.getDefaultDisplay();display.getMetrics(dm);final int screenWidth &#61; dm.widthPixels;String url &#61; mData.get(position).getResults().get(0).getUrl();Glide.with(mContext).load(url).asBitmap().diskCacheStrategy(DiskCacheStrategy.ALL).into(new SimpleTarget(screenWidth, screenWidth) {&#64;Overridepublic void onResourceReady(Bitmap resource, GlideAnimationsuper Bitmap> glideAnimation) {if(holder.getAdapterPosition() !&#61; RecyclerView.NO_POSITION) {if (mData.get(holder.getAdapterPosition()).getHeight() <&#61; 0) {int width &#61; resource.getWidth();int height &#61; resource.getHeight();int realHeight &#61; screenWidth * height / width / 2;mData.get(holder.getAdapterPosition()).setHeight(realHeight);ViewGroup.LayoutParams lp &#61; holder.mIvMm.getLayoutParams();lp.height &#61; realHeight;if(width 2)lp.width &#61; screenWidth / 2;}holder.mIvMm.setImageBitmap(resource);}}});
        }


五、Demo下载&#xff1a;

     源码链接


个人公众号&#xff1a;每日推荐一篇技术博客&#xff0c;坚持每日进步一丢丢…欢迎关注&#xff0c;想建个微信群&#xff0c;主要讨论安卓和Java语言&#xff0c;一起打基础、用框架、学设计模式&#xff0c;菜鸡变菜鸟&#xff0c;菜鸟再起飞&#xff0c;愿意一起努力的话可以公众号留言&#xff0c;谢谢…




推荐阅读
  • 在Android开发中,使用Picasso库可以实现对网络图片的等比例缩放。本文介绍了使用Picasso库进行图片缩放的方法,并提供了具体的代码实现。通过获取图片的宽高,计算目标宽度和高度,并创建新图实现等比例缩放。 ... [详细]
  • 本文介绍了设计师伊振华受邀参与沈阳市智慧城市运行管理中心项目的整体设计,并以数字赋能和创新驱动高质量发展的理念,建设了集成、智慧、高效的一体化城市综合管理平台,促进了城市的数字化转型。该中心被称为当代城市的智能心脏,为沈阳市的智慧城市建设做出了重要贡献。 ... [详细]
  • 本文主要解析了Open judge C16H问题中涉及到的Magical Balls的快速幂和逆元算法,并给出了问题的解析和解决方法。详细介绍了问题的背景和规则,并给出了相应的算法解析和实现步骤。通过本文的解析,读者可以更好地理解和解决Open judge C16H问题中的Magical Balls部分。 ... [详细]
  • This article discusses the efficiency of using char str[] and char *str and whether there is any reason to prefer one over the other. It explains the difference between the two and provides an example to illustrate their usage. ... [详细]
  • Android自定义控件绘图篇之Paint函数大汇总
    本文介绍了Android自定义控件绘图篇中的Paint函数大汇总,包括重置画笔、设置颜色、设置透明度、设置样式、设置宽度、设置抗锯齿等功能。通过学习这些函数,可以更好地掌握Paint的用法。 ... [详细]
  • 本文介绍了在Android开发中使用软引用和弱引用的应用。如果一个对象只具有软引用,那么只有在内存不够的情况下才会被回收,可以用来实现内存敏感的高速缓存;而如果一个对象只具有弱引用,不管内存是否足够,都会被垃圾回收器回收。软引用和弱引用还可以与引用队列联合使用,当被引用的对象被回收时,会将引用加入到关联的引用队列中。软引用和弱引用的根本区别在于生命周期的长短,弱引用的对象可能随时被回收,而软引用的对象只有在内存不够时才会被回收。 ... [详细]
  • 如何自行分析定位SAP BSP错误
    The“BSPtag”Imentionedintheblogtitlemeansforexamplethetagchtmlb:configCelleratorbelowwhichi ... [详细]
  • Spring特性实现接口多类的动态调用详解
    本文详细介绍了如何使用Spring特性实现接口多类的动态调用。通过对Spring IoC容器的基础类BeanFactory和ApplicationContext的介绍,以及getBeansOfType方法的应用,解决了在实际工作中遇到的接口及多个实现类的问题。同时,文章还提到了SPI使用的不便之处,并介绍了借助ApplicationContext实现需求的方法。阅读本文,你将了解到Spring特性的实现原理和实际应用方式。 ... [详细]
  • 拥抱Android Design Support Library新变化(导航视图、悬浮ActionBar)
    转载请注明明桑AndroidAndroid5.0Loollipop作为Android最重要的版本之一,为我们带来了全新的界面风格和设计语言。看起来很受欢迎࿰ ... [详细]
  • 自动轮播,反转播放的ViewPagerAdapter的使用方法和效果展示
    本文介绍了如何使用自动轮播、反转播放的ViewPagerAdapter,并展示了其效果。该ViewPagerAdapter支持无限循环、触摸暂停、切换缩放等功能。同时提供了使用GIF.gif的示例和github地址。通过LoopFragmentPagerAdapter类的getActualCount、getActualItem和getActualPagerTitle方法可以实现自定义的循环效果和标题展示。 ... [详细]
  • FeatureRequestIsyourfeaturerequestrelatedtoaproblem?Please ... [详细]
  • 成功安装Sabayon Linux在thinkpad X60上的经验分享
    本文分享了作者在国庆期间在thinkpad X60上成功安装Sabayon Linux的经验。通过修改CHOST和执行emerge命令,作者顺利完成了安装过程。Sabayon Linux是一个基于Gentoo Linux的发行版,可以将电脑快速转变为一个功能强大的系统。除了作为一个live DVD使用外,Sabayon Linux还可以被安装在硬盘上,方便用户使用。 ... [详细]
  • 本文由编程笔记#小编整理,主要介绍了关于数论相关的知识,包括数论的算法和百度百科的链接。文章还介绍了欧几里得算法、辗转相除法、gcd、lcm和扩展欧几里得算法的使用方法。此外,文章还提到了数论在求解不定方程、模线性方程和乘法逆元方面的应用。摘要长度:184字。 ... [详细]
  • Java 11相对于Java 8,OptaPlanner性能提升有多大?
    本文通过基准测试比较了Java 11和Java 8对OptaPlanner的性能提升。测试结果表明,在相同的硬件环境下,Java 11相对于Java 8在垃圾回收方面表现更好,从而提升了OptaPlanner的性能。 ... [详细]
  • 本文介绍了使用Python编写购物程序的实现步骤和代码示例。程序启动后,用户需要输入工资,并打印商品列表。用户可以根据商品编号选择购买商品,程序会检测余额是否充足,如果充足则直接扣款,否则提醒用户。用户可以随时退出程序,在退出时打印已购买商品的数量和余额。附带了完整的代码示例。 ... [详细]
author-avatar
A600810
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有