注册
环信即时通讯云

环信即时通讯云

单聊、群聊、聊天室...
环信开发文档

环信开发文档

环信FAQ

环信FAQ

集成常见问题及答案
RTE开发者社区

RTE开发者社区

汇聚音视频领域技术干货,分享行业资讯
技术讨论区

技术讨论区

技术交流、答疑
资源下载

资源下载

收集了海量宝藏开发资源
iOS Library

iOS Library

不需要辛辛苦苦的去找轮子, 这里都有
Android Library

Android Library

不需要辛辛苦苦的去找轮子, 这里都有

reveal动画效果的库:EasyReveal

从名字就知道,这是一个提供reveal动画效果的库,它的厉害之处在于可以提供不同尺寸、不同形状的reveal动画,并且还可以在定义它在屏幕任意位置开始和结束动画。4.1 如何使用?在build.gradle 中添加如下依赖:dependencies { .....
继续阅读 »

从名字就知道,这是一个提供reveal动画效果的库,它的厉害之处在于可以提供不同尺寸、不同形状的reveal动画,并且还可以在定义它在屏幕任意位置开始和结束动画。

4.1 如何使用?

build.gradle 中添加如下依赖:

dependencies {
...
implementation 'com.github.Chrisvin:EasyReveal:1.2'
}

然后,xml中,需要添加显示或者隐藏动画的View应该包裹在EasyRevealLinearLayout中:







也可以在代码中添加:

val revealLayout = EasyRevealLinearLayout(this)
// Set the ClipPathProvider that is used to clip the view for reveal animation
revealLayout.clipPathProvider = StarClipPathProvider(numberOfPoints = 6)
// Set the duration taken for reveal animation
revealLayout.revealAnimationDuration = 1500
// Set the duration taken for hide animation
revealLayout.hideAnimationDuration = 2000
// Set listener to get updates during reveal/hide animation
revealLayout.onUpdateListener = object: RevealLayout.OnUpdateListener {
override fun onUpdate(percent: Float) {
Toast.makeText(this@MainActivity, "Revealed percent: $percent", Toast.LENGTH_SHORT).show()
}
}
// Start reveal animation
revealLayout.reveal()
// Start hide animation
revealLayout.hide()
4.2效果图
Emotion DialogDrake DialogEmoji Dialog

更多详细使用信息请看Github: https://github.com/Chrisvin/EasyReveal

下载地址:EasyReveal-master.zip

收起阅读 »

美观而时尚的AlterDialog库:AestheticDialogs

这是一个美观而时尚的AlterDialog库,目前可支持六种不同的对话框,如:Flash DialogConnectify DialogToaster DialogEmotion DialogDrake DialogEmoji Dialog并且啊,还提供了暗黑...
继续阅读 »

这是一个美观而时尚的AlterDialog库,目前可支持六种不同的对话框,如:

  • Flash Dialog
  • Connectify Dialog
  • Toaster Dialog
  • Emotion Dialog
  • Drake Dialog
  • Emoji Dialog
    并且啊,还提供了暗黑模式的适配。
3.1 如何使用?

build.gradle 中添加如下依赖:

dependencies {
...
implementation 'com.github.gabriel-TheCode:AestheticDialogs:1.1.0'
}

代码中,显示不同种类的对话框则调用对应的方法就好

Flash:

AestheticDialog.showFlashDialog(this, "Your dialog Title", "Your message", AestheticDialog.SUCCESS);
AestheticDialog.showFlashDialog(this, "Your dialog Title", "Your message", AestheticDialog.ERROR);

Connectify:

AestheticDialog.showConnectify(this,"Your message", AestheticDialog.SUCCESS);
AestheticDialog.showConnectify(this, "Your message", AestheticDialog.ERROR);

/// Dark Theme
AestheticDialog.showConnectifyDark(this,"Your message",AestheticDialog.SUCCESS);
AestheticDialog.showConnectifyDark(this, "Your message", AestheticDialog.ERROR);

Toaster:

 AestheticDialog.showToaster(this, "Your dialog Title", "Your message", AestheticDialog.ERROR);
AestheticDialog.showToaster(this, "Your dialog Title", "Your message", AestheticDialog.SUCCESS);
AestheticDialog.showToaster(this, "Your dialog Title", "Your message", AestheticDialog.WARNING);
AestheticDialog.showToaster(this, "Your dialog Title", "Your message", AestheticDialog.INFO);

/// Dark Theme
AestheticDialog.showToasterDark(this, "Your dialog Title", "Your message", AestheticDialog.ERROR);
AestheticDialog.showToasterDark(this, "Your dialog Title", "Your message", AestheticDialog.SUCCESS);
AestheticDialog.showToasterDark(this, "Your dialog Title", "Your message", AestheticDialog.WARNING);
AestheticDialog.showToasterDark(this, "Your dialog Title", "Your message", AestheticDialog.INFO);

Drake :

 AestheticDialog.showDrake(this, AestheticDialog.SUCCESS);
AestheticDialog.showDrake(this, AestheticDialog.ERROR);

Emoji :

 AestheticDialog.showEmoji(this,"Your dialog Title", "Your message", AestheticDialog.SUCCESS);
AestheticDialog.showEmoji(this, "Your dialog Title", "Your message", AestheticDialog.ERROR);

/// Dark Theme
AestheticDialog.showEmojiDark(this,"Your dialog Title", "Your message", AestheticDialog.SUCCESS);
AestheticDialog.showEmojiDark(this, "Your dialog Title", "Your message", AestheticDialog.ERROR);

Emotion :

 AestheticDialog.showEmotion(this,"Your dialog Title", "Your message", AestheticDialog.SUCCESS);
AestheticDialog.showEmotion(this, "Your dialog Title", "Your message", AestheticDialog.ERROR);

Rainbow :

 AestheticDialog.showRainbow(this,"Your dialog Title", "Your message", AestheticDialog.SUCCESS);
AestheticDialog.showRainbow(this,"Your dialog Title", "Your message", AestheticDialog.ERROR);
AestheticDialog.showRainbow(this,"Your dialog Title", "Your message", AestheticDialog.WARNING);
AestheticDialog.showRainbow(this,"Your dialog Title", "Your message", AestheticDialog.INFO);
3.2 效果如下
Flash DialogConnectify DialogToaster Dialog
d1.gifd2.gifd3.gif
Emotion DialogDrake DialogEmoji Dialog

d5.gifd6.gif

更多详情使用方法请看Github:https://github.com/gabriel-TheCode/AestheticDialogs

下载地址:AestheticDialogs-master.zip

收起阅读 »

炫酷的显示或者隐藏一个布局:Flourish

Flourish提供了一个炫酷的方式来显示或者隐藏一个布局,实现方式也很简单,就是对View或者布局进行了包装,通过构建者模式来提供api给上层调用。就像使用dialog一样,调用show和dissmiss方法来显示和隐藏。此外,通过这些类,我们还可以自定义动...
继续阅读 »

Flourish提供了一个炫酷的方式来显示或者隐藏一个布局,实现方式也很简单,就是对View或者布局进行了包装,通过构建者模式来提供api给上层调用。就像使用dialog一样,调用showdissmiss方法来显示和隐藏。此外,通过这些类,我们还可以自定义动画(正常,加速,反弹),或为布局方向设置我们自己的起点(左上,右下等)。

2.1 如何使用?

在build.gradle 中添加如下依赖:

dependencies {
implementation "com.github.skydoves:flourish:1.0.0"
}

然后在代码中,构建布局:

Flourish flourish = new Flourish.Builder(parentLayout)
// sets the flourish layout for showing and dismissing on the parent layout.
.setFlourishLayout(R.layout.layout_flourish_main)
// sets the flourishing animation for showing and dismissing.
.setFlourishAnimation(FlourishAnimation.BOUNCE)
// sets the orientation of the starting point.
.setFlourishOrientation(FlourishOrientation.TOP_LEFT)
// sets a flourishListener for listening changes.
.setFlourishListener(flourishListener)
// sets the flourish layout should be showed on start.
.setIsShowedOnStart(false)
// sets the duration of the flourishing.
.setDuration(800L)
.build();

还提供有更简介的DSL:

val myFlourish = createFlourish(parentLayout) {
setFlourishLayout(R.layout.layout_flourish_main)
setFlourishAnimation(FlourishAnimation.ACCELERATE)
setFlourishOrientation(FlourishOrientation.TOP_RIGHT)
setIsShowedOnStart(true)
setFlourishListener { }
}
2.2 效果图
效果1效果2

更多详细使用请看Github:https://github.com/skydoves/Flourish

下载地址:Flourish-master.zip

收起阅读 »

动画ViewPager库:LiquidSwipe

这是一个很棒的ViewPager库,它在浏览ViewPager的不同页面时,显示波浪的滑动动画,效果非常炫酷。该库的USP是触摸交互的。这意味着在视图中显示类似液体的显示过渡时,应考虑触摸事件。1.1如何使用呢?导入以下Gradle依赖项:implementa...
继续阅读 »

这是一个很棒的ViewPager库,它在浏览ViewPager的不同页面时,显示波浪的滑动动画,效果非常炫酷。该库的USP是触摸交互的。这意味着在视图中显示类似液体的显示过渡时,应考虑触摸事件。

1.1如何使用呢?

导入以下Gradle依赖项:

implementation 'com.github.Chrisvin:LiquidSwipe:1.3'

然后将LiquidSwipeLayout添加为保存fragment布局的容器的根布局:






1.2 效果图
效果1效果2

更多详细使用方法请看Github: https://github.com/Chrisvin/LiquidSwipe

下载地址:LiquidSwipe-master.zip

收起阅读 »

你的Android库是否还在Application中初始化?

通常来说,当我们引入一个第三方库,第一件要做的事情是在Application中的onCreate传入context初始化这个库 😞。但是为什么像一些库如Firebase🔥,初始化的时候并不需要在Application中初始化呢?今天我们就来探索一下这个问题 🧐...
继续阅读 »

通常来说,当我们引入一个第三方库,第一件要做的事情是在Application中的onCreate传入context初始化这个库 😞。但是为什么像一些库如Firebase🔥,初始化的时候并不需要在Application中初始化呢?今天我们就来探索一下这个问题 🧐


Android库的初始化


举个栗子,我们需要在app中国呢使用ARouter,在使用前需要初始化传入context,因此如果没有application时我们要创建一个:


class MainApplication : Application() {
override fun onCreate(){
super.onCreate()
ARouter.init(mApplication);
}
}
复制代码

然后要在清单文件 AndroidManifest.xml 中声明才会执行 :


<application
android:name=".MainApplication"
...
复制代码

更多库怎么办


现在想象我们使用了ARouter,友盟统计,Realm,ToastUtils等库时,我们的application可能会是如下形式:


class MainApplication : Application() {
override fun onCreate(){
super.onCreate()
ARouter.init(this)
UMConfigure.init(this,...)
Realm.init(this)
ToastUtils.init(this)
}
}
复制代码

在项目中, 仅仅为了初始化一些库,我就必须得新建Application并且在onCreate中调用它。(译者:也许你认为这也没什么,但是如果你自己创建了多个库需要context时,你每次得预留一个init方法暴露给调用者,使用时又得在application初始化。)



Useless


无需“初始化”的库


如果你的项目加入了Firebase 🔥, 你会发现它并没有要求初始化, 你只要使用它 :


这个数据库访问没有需要context的传入,通过离线访问存储本地。可以猜测它有一个机制获取上下文application context,自动完成初始化。


ContentProvider & Manifest-Merger


developer.android.com/studio/buil…



你的Apk文件只包含一个清单文件AndroidManifest.xml,但是你的Android Studio项目可能会有多个源集(main source set),构建变体(build variants),导入的库(imported libraries)构成。因此在编译构建app时,gradle插件会将多个manifest文件合并到一个清单文件中去。



我们可以看下合并后的清单文件(目录如下):



app/build/intermediates/merged_manifests/MY_APP/processMY_APPDebugManifest/merged/AndroidManifest.xml



我们可以发现一个关于Firebase库的provider被引入到清单文件中:


使用Android Studio点击打开FirebaseInitProvider, 我们知道了这个provider通过this.getContext()来访问上下文。

内容提供者ContentsProviders会直接在Application创建后完成初始化,因此通过它来完成library的初始化不失为一个好办法。


自动初始化我们的库


如果我们自定义了ToastUtils库需要初始化,我们自己提供一个Provider :


class ToastInitProvider : ContentProvider() {

override fun onCreate(): Boolean {
ToastUtils.init(context)
return true
}
...
}
复制代码

然后这个库中的AndroidManifest.xml中加入它


<provider
android:name=".ToastInitProvider"
android:authorities="xxx.xx.ToastInitProvider" />
复制代码

然后当我们使用这个ToastUtils库时,无需添加额外的代码在项目中初始化它😎,直接使用它即可:


ToastUtils.show("this is toast")
复制代码
Stetho.getInstance().configure(…)
复制代码

删除Application


如果一些库没有使用InitProviders,我们可以创建它:


class ARouterInitProvider : ContentProvider() {

override fun onCreate(): Boolean {
ARouter.init(this)
return true
}
...
}
class RealmInitProvider : ContentProvider() {

override fun onCreate(): Boolean {
Realm.init(this)
return true
}
...
}
复制代码

然后加入到清单文件AndroidManifest中 :


<provider
android:name=".ARouterInitProvider"
android:authorities="${applicationId}.ARouterInitProvider" />
<provider
android:name=".RealmInitProvider"
android:authorities="${applicationId}.RealmInitProvider" />
复制代码

现在我们可以 移除 这个 MainApplication


Happy Dance


项目地址


https://github.com/florent37/ApplicationProvider

作者:星星y
链接:https://juejin.cn/post/6844903967864913933
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 收起阅读 »

Android选择图片库-TakePhoto

TakePhoto是一款用于在Android设备上获取照片(拍照或从相册、文件中选择)、裁剪图片、压缩图片的开源工具库,目前最新版本4.1.0。 3.0以下版本及API说明,详见TakePhoto2.0+。TakePhoto交流平台:QQ群:556387607...
继续阅读 »

TakePhoto是一款用于在Android设备上获取照片(拍照或从相册、文件中选择)、裁剪图片、压缩图片的开源工具库,目前最新版本4.1.0。 3.0以下版本及API说明,详见TakePhoto2.0+

TakePhoto交流平台:QQ群:556387607(群1,未满)

V4.0

  • 支持通过相机拍照获取图片
  • 支持从相册选择图片
  • 支持从文件选择图片
  • 支持批量图片选取
  • 支持图片压缩以及批量图片压缩
  • 支持图片裁切以及批量图片裁切
  • 支持照片旋转角度自动纠正
  • 支持自动权限管理(无需关心SD卡及摄像头权限等问题)
  • 支持对裁剪及压缩参数个性化配置
  • 提供自带裁剪工具(可选)
  • 支持智能选取及裁剪异常处理
  • 支持因拍照Activity被回收后的自动恢复
  • 支持Android8.1
  • +支持多种压缩工具
  • +支持多种图片选择工具

目录

安装说明

Gradle:

    compile 'com.jph.takephoto:takephoto_library:4.1.0'

Maven:

<dependency>
<groupId>com.jph.takephoto</groupId>
<artifactId>takephoto_library</artifactId>
<version>4.1.0</version>
<type>pom</type>
</dependency>

使用说明

使用TakePhoto有以下两种方式:

方式一:通过继承的方式

  1. 继承TakePhotoActivityTakePhotoFragmentActivityTakePhotoFragment三者之一。
  2. 通过getTakePhoto()获取TakePhoto实例进行相关操作。
  3. 重写以下方法获取结果
 void takeSuccess(TResult result);
void takeFail(TResult result,String msg);
void takeCancel();

此方式使用简单,满足的大部分的使用需求,具体使用详见simple。如果通过继承的方式无法满足实际项目的使用,可以通过下面介绍的方式。

方式二:通过组装的方式

可参照:TakePhotoActivity,以下为主要步骤:

1.实现TakePhoto.TakeResultListener,InvokeListener接口。

2.在 onCreate,onActivityResult,onSaveInstanceState方法中调用TakePhoto对用的方法。

3.重写onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults),添加如下代码。

  @Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
//以下代码为处理Android6.0、7.0动态权限所需
TPermissionType type=PermissionManager.onRequestPermissionsResult(requestCode,permissions,grantResults);
PermissionManager.handlePermissionsResult(this,type,invokeParam,this);
}

4.重写TPermissionType invoke(InvokeParam invokeParam)方法,添加如下代码:

 @Override
public TPermissionType invoke(InvokeParam invokeParam) {
TPermissionType type=PermissionManager.checkPermission(TContextWrap.of(this),invokeParam.getMethod());
if(TPermissionType.WAIT.equals(type)){
this.invokeParam=invokeParam;
}
return type;
}

5.添加如下代码获取TakePhoto实例:

   /**
* 获取TakePhoto实例
* @return
*/
public TakePhoto getTakePhoto(){
if (takePhoto==null){
takePhoto= (TakePhoto) TakePhotoInvocationHandler.of(this).bind(new TakePhotoImpl(this,this));
}
return takePhoto;
}

自定义UI

TakePhoto不仅支持对相关参数的自定义,也支持对UI的自定义,下面就像大家介绍如何自定义TakePhoto的相册与裁剪工具的UI。

自定义相册

如果TakePhoto自带相册的UI不符合你应用的主题的话,你可以对它进行自定义。方法如下:

自定义Toolbar

在“res/layout”目录中创建一个名为“toolbar.xml”的布局文件,内容如下:

<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.Toolbar xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:theme="@style/CustomToolbarTheme"
android:background="#ffa352">
</android.support.v7.widget.Toolbar>

在“toolbar.xml”文件中你可以指定TakePhoto自带相册的主题以及Toolbar的背景色。

自定义状态栏

在“res/values”目录中创建一个名为“colors.xml”的资源文件,内容如下:

<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="multiple_image_select_primaryDark">#212121</color>
</resources>

通过上述方式便可以自定义状态栏的颜色。

自定义提示文字

在“res/values”目录的“string.xml”文件冲添加如下代码:

<resources>    
<string name="album_view">选择图片</string>
<string name="image_view">单击选择</string>
<string name="add">确定</string>
<string name="selected">已选</string>
<string name="limit_exceeded">最多能选 %d 张</string>
</resources>

重写上述代码,便可以自定义TakePhoto自带相册的提示文字。

自定义裁切工具

在“res/layout”目录中创建一个名为“crop__activity_crop.xml”与“crop__layout_done_cancel.xml”的布局文件,内容如下:

crop__activity_crop.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.soundcloud.android.crop.CropImageView
android:id="@+id/crop_image"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignParentTop="true"
android:background="@drawable/crop__texture"
android:layout_above="@+id/done_cancel_bar" />
<include
android:id="@+id/done_cancel_bar"
android:layout_alignParentBottom="true"
layout="@layout/crop__layout_done_cancel"
android:layout_height="50dp"
android:layout_width="match_parent" />
</RelativeLayout>

crop__layout_done_cancel.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
style="@style/Crop.DoneCancelBar">
<FrameLayout
android:id="@+id/btn_cancel"
style="@style/Crop.ActionButton">
<TextView style="@style/Crop.ActionButtonText.Cancel" />
</FrameLayout>
<FrameLayout
android:id="@+id/btn_done"
style="@style/Crop.ActionButton">
<TextView style="@style/Crop.ActionButtonText.Done" />
</FrameLayout>
</LinearLayout>

重写上述代码,便可以自定义TakePhoto裁切工具的UI。

API

获取图片

TakePhoto提供拍照,从相册选择,从文件中选择三种方式获取图片。

API:

/**
* 从文件中获取图片(不裁剪)
*/
void onPickFromDocuments();
/**
* 从相册中获取图片(不裁剪)
*/
void onPickFromGallery();
/**
* 从相机获取图片(不裁剪)
* @param outPutUri 图片保存的路径
*/
void onPickFromCapture(Uri outPutUri);
/**
* 图片多选
* @param limit 最多选择图片张数的限制
**/
void onPickMultiple(int limit);

以上三种方式均提供对应的裁剪API,详见:裁剪图片
注:
由于不同Android Rom厂商对系统有不同程度的定制,有可能导致某种选择图片的方式不支持,所以为了提高TakePhoto的兼容性,当某种选的图片的方式不支持时,TakePhoto会自动切换成使用另一种选择图片的方式进行图片选择。

裁剪图片

API

TakePhoto支持对图片进行裁剪,无论是拍照的照片,还是从相册、文件中选择的图片。你只需要调用TakePhoto的相应方法即可:

/**
* 从相机获取图片并裁剪
* @param outPutUri 图片裁剪之后保存的路径
* @param options 裁剪配置
*/
void onPickFromCaptureWithCrop(Uri outPutUri, CropOptions options);
/**
* 从相册中获取图片并裁剪
* @param outPutUri 图片裁剪之后保存的路径
* @param options 裁剪配置
*/
void onPickFromGalleryWithCrop(Uri outPutUri, CropOptions options);
/**
* 从文件中获取图片并裁剪
* @param outPutUri 图片裁剪之后保存的路径
* @param options 裁剪配置
*/
void onPickFromDocumentsWithCrop(Uri outPutUri, CropOptions options);
/**
* 图片多选,并裁切
* @param limit 最多选择图片张数的限制
* @param options 裁剪配置
* */
void onPickMultipleWithCrop(int limit, CropOptions options);

对指定图片进行裁剪

另外,TakePhoto也支持你对指定图片进行裁剪:

/**
* 裁剪图片
* @param imageUri 要裁剪的图片
* @param outPutUri 图片裁剪之后保存的路径
* @param options 裁剪配置
*/
void onCrop(Uri imageUri, Uri outPutUri, CropOptions options)throws TException;
/**
* 裁剪多张图片
* @param multipleCrop 要裁切的图片的路径以及输出路径
* @param options 裁剪配置
*/
void onCrop(MultipleCrop multipleCrop, CropOptions options)throws TException;

CropOptions

CropOptions是用于裁剪的配置类,通过它你可以对图片的裁剪比例,最大输出大小,以及是否使用TakePhoto自带的裁剪工具进行裁剪等,进行个性化配置。

Usage:

 CropOptions cropOptions=new CropOptions.Builder().setAspectX(1).setAspectY(1).setWithOwnCrop(true).create();  
getTakePhoto().onPickFromDocumentsWithCrop(imageUri,cropOptions);
//
getTakePhoto().onCrop(imageUri,outPutUri,cropOptions);

注:
由于不同Android Rom厂商对系统有不同程度的定制,有可能系统中没有自带或第三方的裁剪工具,所以为了提高TakePhoto的兼容性,当系统中没有自带或第三方裁剪工具时,TakePhoto会自动切换到使用TakePhoto自带的裁剪工具进行裁剪。

另外TakePhoto4.0+支持指定使用TakePhoto自带相册,如:takePhoto.setTakePhotoOptions(new TakePhotoOptions.Builder().setWithOwnGallery(true).create()); 详情可参考:Demo

压缩图片

你可以选择是否对图片进行压缩处理,你只需要告诉它你是否要启用压缩功能以及CompressConfig即可。

API

 /**
* 启用图片压缩
* @param config 压缩图片配置
* @param showCompressDialog 压缩时是否显示进度对话框
* @return
*/
void onEnableCompress(CompressConfig config,boolean showCompressDialog);

Usage:

TakePhoto takePhoto=getTakePhoto();
takePhoto.onEnableCompress(compressConfig,true);
takePhoto.onPickFromGallery();

如果你启用了图片压缩,TakePhoto会使用CompressImage对图片进行压缩处理,CompressImage目前支持对图片的尺寸以及图片的质量进行压缩。默认情况下,CompressImage开启了尺寸与质量双重压缩。

对指定图片进行压缩

另外,你也可以对指定图片进行压缩:
Usage:

new CompressImageImpl(compressConfig,result.getImages(), new CompressImage.CompressListener() {
@Override
public void onCompressSuccess(ArrayList<TImage> images) {
//图片压缩成功
}
@Override
public void onCompressFailed(ArrayList<TImage> images, String msg) {
//图片压缩失败
}
}).compress();

CompressConfig

CompressConfig是用于图片压缩的配置类,你可以通过CompressConfig.Builder对图片压缩后的尺寸以及质量进行相关设置。如果你想改变压缩的方式可以通过CompressConfig.Builder进行相关设置。
Usage:

CompressConfig compressConfig=new CompressConfig.Builder().setMaxSize(50*1024).setMaxPixel(800).create();

指定压缩工具

使用TakePhoto压缩工具进行压缩:

CompressConfig config=new CompressConfig.Builder()
.setMaxSize(maxSize)
.setMaxPixel(width>=height? width:height)
.create();
takePhoto.onEnableCompress(config,showProgressBar);

使用Luban进行压缩:

LubanOptions option=new LubanOptions.Builder()
.setGear(Luban.CUSTOM_GEAR)
.setMaxHeight(height)
.setMaxWidth(width)
.setMaxSize(maxSize)
.create();
CompressConfig config=CompressConfig.ofLuban(option);
takePhoto.onEnableCompress(config,showProgressBar);

详情可参考Demo:CustomHelper.java


原文链接:https://github.com/crazycodeboy/TakePhoto

代码下载:TakePhoto-master.zip

收起阅读 »

最接近朋友圈的图片压缩算法-Luban

Luban(鲁班) —— Android图片压缩工具,仿微信朋友圈压缩策略。Luban-turbo —— 鲁班项目的turbo版本,查看trubo分支。写在前面家境贫寒,工作繁忙。只能不定期更新,还望网友们见谅!项目描述目前做App开发总绕...
继续阅读 »

Luban(鲁班) —— Android图片压缩工具,仿微信朋友圈压缩策略。

Luban-turbo —— 鲁班项目的turbo版本,查看trubo分支

写在前面

家境贫寒,工作繁忙。只能不定期更新,还望网友们见谅!

项目描述

目前做App开发总绕不开图片这个元素。但是随着手机拍照分辨率的提升,图片的压缩成为一个很重要的问题。单纯对图片进行裁切,压缩已经有很多文章介绍。但是裁切成多少,压缩成多少却很难控制好,裁切过头图片太小,质量压缩过头则显示效果太差。

于是自然想到App巨头“微信”会是怎么处理,Luban(鲁班)就是通过在微信朋友圈发送近100张不同分辨率图片,对比原图与微信压缩后的图片逆向推算出来的压缩算法。

因为有其他语言也想要实现Luban,所以描述了一遍算法步骤

因为是逆向推算,效果还没法跟微信一模一样,但是已经很接近微信朋友圈压缩后的效果,具体看以下对比!

效果与对比

内容原图LubanWechat
截屏 720P720*1280,390k720*1280,87k720*1280,56k
截屏 1080P1080*1920,2.21M1080*1920,104k1080*1920,112k
拍照 13M(4:3)3096*4128,3.12M1548*2064,141k1548*2064,147k
拍照 9.6M(16:9)4128*2322,4.64M1032*581,97k1032*581,74k
滚动截屏1080*6433,1.56M1080*6433,351k1080*6433,482k

导入

implementation 'top.zibin:Luban:1.1.8'

使用

方法列表

方法描述
load传入原图
filter设置开启压缩条件
ignoreBy不压缩的阈值,单位为K
setFocusAlpha设置是否保留透明通道
setTargetDir缓存压缩图片路径
setCompressListener压缩回调接口
setRenameListener压缩前重命名接口

异步调用

Luban内部采用IO线程进行图片压缩,外部调用只需设置好结果监听即可:

Luban.with(this)
.load(photos)
.ignoreBy(100)
.setTargetDir(getPath())
.filter(new CompressionPredicate() {
@Override
public boolean apply(String path) {
return !(TextUtils.isEmpty(path) || path.toLowerCase().endsWith(".gif"));
}
})
.setCompressListener(new OnCompressListener() {
@Override
public void onStart() {
// TODO 压缩开始前调用,可以在方法内启动 loading UI
}

@Override
public void onSuccess(File file) {
// TODO 压缩成功后调用,返回压缩后的图片文件
}

@Override
public void onError(Throwable e) {
// TODO 当压缩过程出现问题时调用
}
}).launch();

同步调用

同步方法请尽量避免在主线程调用以免阻塞主线程,下面以rxJava调用为例

Flowable.just(photos)
.observeOn(Schedulers.io())
.map(new Function<List<String>, List<File>>() {
@Override public List<File> apply(@NonNull List<String> list) throws Exception {
// 同步方法直接返回压缩后的文件
return Luban.with(MainActivity.this).load(list).get();
}
})
.observeOn(AndroidSchedulers.mainThread())
.subscribe();


代码下载:Luban-master.zip

原文链接:https://github.com/Curzibn/Luban

收起阅读 »

okhttp 上传下载进度监听-ProgressManager

ProgressManager 一行代码即可监听 App 中所有网络链接的上传以及下载进度,包括 Glide 的图片加载进度,实现原理类似 EventBus,你可在 App 中的任...
继续阅读 »

ProgressManager 一行代码即可监听 App 中所有网络链接的上传以及下载进度,包括 Glide 的图片加载进度,实现原理类似 EventBus,你可在 App 中的任何地方,将多个监听器,以 Url 地址作为标识符,注册到本框架,当此 Url 地址存在下载或者上传的动作时,框架会主动调用所有使用此 Url 地址注册过的监听器,达到多个模块的同步更新.

Feature

  • 使用简单,只需一行代码即可实现进度监听.
  • 多平台支持,支持 Okhttp , Retrofit , Glide ,使用 Okhttp 原生 Api ,不存在兼容问题.
  • 低耦合,实际请求端和进度接收端并不存在直接或间接的关联关系,即可以在 App 任何地方接收进度信息.
  • 侵入性低,使用本框架你并不需要更改之前进行上传或下载的代码,即使用或不使用本框架并不会影响到原有的代码.
  • 多端同步,同一个数据源的上传或下载进度可以指定多个不同的接收端,少去了使用 EventBus 实现多个端口同步更新进度.
  • 支持多文件上传.
  • 支持 URL 重定向.
  • 自动管理监听器,少去了手动注销监听器的烦恼.
  • 默认运行在主线层,少去了切换线程的烦恼.
  • 轻量级框架,不包含任何三方库,体积极小.

Download

 implementation 'me.jessyan:progressmanager:1.5.0'

Usage

Step 1

 // 构建 OkHttpClient 时,将 OkHttpClient.Builder() 传入 with() 方法,进行初始化配置
OkHttpClient = ProgressManager.getInstance().with(new OkHttpClient.Builder())
.build();

Step 2

 // Glide 下载监听
ProgressManager.getInstance().addResponseListener(IMAGE_URL, getGlideListener());


// Okhttp/Retofit 下载监听
ProgressManager.getInstance().addResponseListener(DOWNLOAD_URL, getDownloadListener());


// Okhttp/Retofit 上传监听
ProgressManager.getInstance().addRequestListener(UPLOAD_URL, getUploadListener());

ProGuard

 -keep class me.jessyan.progressmanager.** { *; }
-keep interface me.jessyan.progressmanager.** { *; }


代码下载:ProgressManager-master 2.zip

原文链接:https://github.com/JessYanCoding/ProgressManager/blob/master/README-zh.md#feature

收起阅读 »

kotlin json解析库moshi

Moshi 是Square公司在2015年6月开源的一个 Json 解析库,相对于Gson,FastJson等老牌解析库而言,Moshi不仅支持对Kotlin的解析,并且提供了Reflection跟Annotion两种解析Kotl...
继续阅读 »

Moshi 是Square公司在2015年6月开源的一个 Json 解析库,相对于Gson,FastJson等老牌解析库而言,Moshi不仅支持对Kotlin的解析,并且提供了Reflection跟Annotion两种解析Kotlin的方法,除此之外,Moshi最大的改变在于支持自定义JsonAdapter,能够将Json的Value转换成任意你需要的类型。

基本用法之Java

Dependency

implementation 'com.squareup.moshi:moshi:1.7.0'
复制代码

Bean

String json = ...;
Moshi moshi = new Moshi.Builder().build();
JsonAdapter<Bean> jsonAdapter = moshi.adapter(Bean.class);
//Deserialize
Bean bean = jsonAdapter.fromJson(json);
//Serialize
String json = jsonAdapter.toJson(bean);
复制代码

List

Moshi moshi = new Moshi.Builder().build();
Type listOfCardsType = Types.newParameterizedType(List.class, Bean.class);
JsonAdapter<List<Bean>> jsonAdapter = moshi.adapter(listOfCardsType);
//Deserialize
List<Bean> beans = jsonAdapter.fromJson(json);
//Serialize
String json = jsonAdapter.fromJson(json);
复制代码

Map

Moshi moshi = new Moshi.Builder().build();
ParameterizedType newMapType = Types.newParameterizedType(Map.class, String.class, Integer.class);
JsonAdapter<Map<String,Integer>> jsonAdapter = moshi.adapter(newMapType);
//Deserialize
Map<String,Integer> beans = jsonAdapter.fromJson(json);
//Serialize
String json = jsonAdapter.fromJson(json);
复制代码

Others

  • @json:Key转换
  • transitent:跳过该字段不解析
public final class Bean {
@Json(name = "lucky number") int luckyNumber;
@Json(name = "objec") int data;
@Json(name = "toatl_price") String totolPrice;
private transient int total;//jump the field
}
复制代码

基本用法之Kotlin

相对于 Java 只能通过反射进行解析,针对Kotlin,Moshi提供了两种解析方式,一种是通过 Reflection ,一种是通过 Annotation ,你可以采用其中的一种,也可以两种都使用,下面分别介绍下这两种解析方式

Dependency

implementation 'com.squareup.moshi:moshi-kotlin:1.7.0'
复制代码

Reflection

Data类

data class ConfigBean(
var isGood: Boolean = false,
var title: String = "",
var type: CustomType = CustomType.DEFAULT
)
复制代码

开始解析

val moshi = Moshi.Builder()
.add(KotlinJsonAdapterFactory())
.build()
复制代码

这种方式会引入Kotlin-Reflect的Jar包,大概有2.5M。

Annotation

上面提到了Reflection,会导致APK体积增大,所以Moshi还提供了另外一种解析方式,就是注解,Moshi的官方叫法叫做Codegen,因为是采用注解生成的,所以除了添加Moshi的Kotlin依赖之外,还需要加上kapt

kapt 'com.squareup.moshi:moshi-kotlin-codegen:1.7.0'
复制代码

改造Data类

给我们的数据类增加JsonClass注解

@JsonClass(generateAdapter = true)
data
class ConfigBean(
var isGood: Boolean = false,
var title: String = "",
var type: CustomType = CustomType.DEFAULT
)
复制代码

这样的话,Moshi会在编译期生成我们需要的JsonAdapter,然后通过JsonReader遍历的方式去解析Json数据,这种方式不仅仅不依赖于反射,而且速度快于Kotlin。

高级用法(JsonAdapter)

JsonAdapter是Moshi有别于Gson,FastJson的最大特点,顾名思义,这是一个Json的转换器,他的主要作用在于将拿到的Json数据转换成任意你想要的类型,Moshi内置了很多JsonAdapter,有如下这些:

Built-in Type Adapters

  • Map :MapJsonAdapter
  • Enums :EnumJsonAdapter
  • Arrays :ArrayJsonAdapter
  • Object :ObjectJsonAdapter
  • String :位于StandardJsonAdapters,采用匿名内部类实现
  • Primitives (int, float, char,boolean) :基本数据类型的Adapter都在StandardJsonAdapters里面,采用匿名内部类实现

Custom Type Adapters

对于一些比较简单规范的数据,使用Moshi内置的JsonAdapter已经完全能够Cover住,但是由于Json只支持基本数据类型传输,所以很多时候不能满足业务上需要,举个例子:

{
"type": 2,
"isGood": 1
"title": "TW9zaGkgaXMgZmxleGlibGU="
}
复制代码

这是一个很普通的Json,包含了5个字段,我们如果按照服务端返回的字段来定义解析的Bean,显然是可以完全解析的,但是我们在实际调用的时候,这些数据并不是很干净,我们还需要处理一下:

  • type :Int类型,我需要Enum,我得定义一个Enum的转换类,去将Int转换成Enum
  • isGood :Int类型,我需要Boolean,所以我用的时候还得将Int转成Boolean
  • title :String类型,这个字段是加密过的,可能是通过AES或者RSA加密,这里我们为了方便测试,只是用Base64对 Moshi is flexible 对进行encode。

对于客户端的同学来说,好像没毛病,以前都是这么干的,如果这种 不干净 的Json少点还好,多了之后就很头疼,每个在用的时候都需要转一遍,很多时候我这么干的时候都觉得浪费时间,而今天有了Moshi之后,我们只需要针对需要转换的类型定义对应的JsonAdapter,达到 一次定义,一劳永逸 的效果,Moshi针对常见的数据类型已经定义了Adapter,但是内置的Adapter现在已经不能满足我们的需求了,所以我们需要自定义JsonAdapter。

实体定义

class ConfigBean {
public CustomType type;
public Boolean isGood;
public String title;
}
复制代码

此处我们定义的数据类型不是根据 服务器 返回的Json数据,而是定义的我们业务需要的格式,那么最终是通过JsonAdapter转换器来完成这个转换,下面开始自定义JsonAdapter。

Int->Enum

CustomType

enum CustomType {
DEFAULT
(0, "DEFAULT"), BAD(1, "BAD"), NORMAL(2, "NORMAL"), GOOD(3, "NORMAL");
public int type;
public String content;
CustomType(int type, String content) {
this.type = type;
this.content = content;
}
}
复制代码

TypeAdapter

定义一个TypeAdapter继承自JsonAdapter,传入对应的泛型,会自动帮我们复写fromJson跟toJson两个方法

public class TypeAdapter  {
@FromJson
public CustomType fromJson(int value) throws IOException {
CustomType type = CustomType.DEFAULT;
switch (value) {
case 1:
type
= CustomType.BAD;
break;
case 2:
type
= CustomType.NORMAL;
break;
case 3:
type
= CustomType.GOOD;
break;
}
return type;
}
@ToJson
public Integer toJson(CustomType value) {
return value != null ? value.type : 0;
}
}

复制代码

至此已经完成Type的转换,接下来我们再以title举个例子,别的基本上都是照葫芦画瓢,没什么难度

StringDecode

TitleAdapter

public class TitleAdapter {
@FromJson
public String fromJson(String value) {
byte[] decode = Base64.getDecoder().decode(value);
return new String(decode);
}
@ToJson
public String toJson(String value) {
return new String(Base64.getEncoder().encode(value.getBytes()));
}
}
复制代码

Int->Boolean

BooleanAdapter

public class BooleanAdapter {
@FromJson
public Boolean fromJson(int value) {
return value == 1;
}
@ToJson
public Integer toJson(Boolean value) {
return value ? 1 : 0;
}
}

复制代码

Adapter测试

下面我们来测试一下

String json = "{\n" + "\"type\": 2,\n" + "\"isGood\": 1,\n"
+ "\"title\": \"TW9zaGkgaXMgZmxleGlibGU=\"\n"+ "}";
Moshi moshi = new Moshi.Builder()
.add(new TypeAdapter())
.add(new TitleAdapter())
.add(new BooleanAdapter())
.build();
JsonAdapter<ConfigBean> jsonAdapter = moshi.adapter(ConfigBean.class);
ConfigBean cofig = jsonAdapter.fromJson(json);
System.out.println("=========Deserialize ========");
System.out.println(cofig);
String cofigJson = jsonAdapter.toJson(cofig);
System.out.println("=========serialize ========");
System.out.println(cofigJson);
复制代码

打印Log

=========Deserialize ========
ConfigBean{type=CustomType{type=2, content='NORMAL'}, isGood=true, title='Moshi is flexible'}
=========serialize ========
{"isGood":1,"title":"TW9zaGkgaXMgZmxleGlibGU=","type":2}
复制代码

符合我们预期的结果,并且我们在开发的时候,只需要将Moshi设置成单例的,一次性将所有的Adapter全部add进去,就可以一劳永逸,然后愉快地进行开发了。

源码解析

Moshi底层采用了 Okio 进行优化,但是上层的JsonReader,JsonWriter等代码是直接从Gson借鉴过来的,所以不再过多分析,主要是就Moshi的两大创新点 JsonAdapter 以及Kotlin的 Codegen 解析重点分析一下。

Builder

Moshi moshi = new Moshi.Builder().add(new BooleanAdapter()).build();
复制代码

Moshi是通过Builder模式进行构建的,支持添加多个JsonAdapter,下面先看看Builder源码

public static final class Builder {
//存储所有Adapter的创建方式,如果没有添加自定义Adapter,则为空
final List<JsonAdapter.Factory> factories = new ArrayList<>();
//添加自定义Adapter,并返回自身
public Builder add(Object adapter) {
return add(AdapterMethodsFactory.get(adapter));
}
//添加JsonAdapter的创建方法到factories里,并返回自身
public Builder add(JsonAdapter.Factory factory) {
factories
.add(factory);
return this;
}
//添加JsonAdapter的创建方法集合到factories里,并返回自身
public Builder addAll(List<JsonAdapter.Factory> factories) {
this.factories.addAll(factories);
return this;
}
//通过Type添加Adapter的创建方法,并返回自身
public <T> Builder add(final Type type, final JsonAdapter<T> jsonAdapter) {
return add(new JsonAdapter.Factory() {
@Override
public @Nullable JsonAdapter<?> create(
Type targetType, Set<? extends Annotation> annotations, Moshi moshi) { return annotations.isEmpty() && Util.typesMatch(type, targetType) ? jsonAdapter : null;
}
});
}
//创建一个Moshi的实例
public Moshi build() {
return new Moshi(this);
}
}
复制代码

通过源码发现Builder保存了所有自定义Adapter的创建方式,然后调用Builder的build方式创建了一个Moshi的实例,下面看一下Moshi的源码。

Moshi

构造方法

Moshi(Builder builder) {
List<JsonAdapter.Factory> factories = new ArrayList<>(
builder
.factories.size() + BUILT_IN_FACTORIES.size());
factories
.addAll(builder.factories);
factories
.addAll(BUILT_IN_FACTORIES);
this.factories = Collections.unmodifiableList(factories);
}
复制代码

构造方法里面创建了factories,然后加入了Builder中的factories,然后又增加了一个BUILT_IN_FACTORIES,我们应该也能猜到这个就是Moshi内置的JsonAdapter,点进去看一下

BUILT_IN_FACTORIES

static final List<JsonAdapter.Factory> BUILT_IN_FACTORIES = new ArrayList<>(5);
static {
BUILT_IN_FACTORIES
.add(StandardJsonAdapters.FACTORY);
BUILT_IN_FACTORIES
.add(CollectionJsonAdapter.FACTORY);
BUILT_IN_FACTORIES
.add(MapJsonAdapter.FACTORY);
BUILT_IN_FACTORIES
.add(ArrayJsonAdapter.FACTORY);
BUILT_IN_FACTORIES
.add(ClassJsonAdapter.FACTORY);
}
复制代码

BUILT_IN_FACTORIES这里面提前用一个静态代码块加入了所有内置的JsonAdapter

JsonAdapter

JsonAdapter<ConfigBean> jsonAdapter = moshi.adapter(ConfigBean.class);
复制代码

不管是我们自定义的JsonAdapter还是Moshi内置的JsonAdapter,最终都是为我们的解析服务的,所以最终所有的JsonAdapter最终汇聚成JsonAdapter,我们看看是怎么生成的,跟一下Moshi的adapter方法,发现最终调用的是下面的方法

public <T> JsonAdapter<T> adapter(Type type, Set<? extends Annotation> annotations,
@Nullable String fieldName) {
type
= canonicalize(type);
// 如果有对应的缓存,那么直接返回缓存
Object cacheKey = cacheKey(type, annotations);
synchronized (adapterCache) {
JsonAdapter<?> result = adapterCache.get(cacheKey);
if (result != null) return (JsonAdapter<T>) result;
}

boolean success = false;
JsonAdapter<T> adapterFromCall = lookupChain.push(type, fieldName, cacheKey);
try {
if (adapterFromCall != null)
return adapterFromCall;
// 遍历Factories,直到命中泛型T的Adapter
for (int i = 0, size = factories.size(); i < size; i++) {
JsonAdapter<T> result = (JsonAdapter<T>) factories.get(i).create(type, annotations, this);
if (result == null) continue;
lookupChain
.adapterFound(result);
success
= true;
return result;
}
}
}
复制代码

最开始看到这里,我比较奇怪,不太确定我的Config命中了哪一个JsonAdapter,最终通过断点追踪,发现了是命中了 ClassJsonAdapter ,既然命中了他,那么我们就看一下他的具体实现

ClassJsonAdapter

构造方法

final class ClassJsonAdapter<T> extends JsonAdapter<T> {
public static final JsonAdapter.Factory FACTORY = new JsonAdapter.Factory() {
@Override public @Nullable JsonAdapter<?> create(
Type type, Set<? extends Annotation> annotations, Moshi moshi) {
//省略了很多异常判断代码
Class<?> rawType = Types.getRawType(type);
//获取Class的所有类型
ClassFactory<Object> classFactory = ClassFactory.get(rawType);
Map<String, FieldBinding<?>> fields = new TreeMap<>();
for (Type t = type; t != Object.class; t = Types.getGenericSuperclass(t)) {
//创建Moshi跟Filed的绑定关系,便于解析后赋值
createFieldBindings
(moshi, t, fields);
}
return new ClassJsonAdapter<>(classFactory, fields).nullSafe();
}
}
复制代码

当我们拿到一个JsonAdapter的时候,基本上所有的构建都已经完成,此时可以进行Deserialize 或者Serialize 操作,先看下Deserialize 也就是fromjson方法

JsonReader&JsonWriter

对于Java的解析,Moshi并没有在传输效率上进行显著的提升,只是底层的IO操作采用的是Okio,Moshi的创新在于灵活性上面,也就是JsonAdapter,而且Moshi的官方文档上面也提到了

Moshi uses the same streaming and binding mechanisms as Gson . If you’re a Gson user you’ll find Moshi works similarly. If you try Moshi and don’t love it, you can even migrate to Gson without much violence!

所以这里的JsonReader跟JsonWriter说白了都是从Gson那里直接借鉴过来的,就是这么坦诚。

fromjson

ConfigBean cofig = jsonAdapter.fromJson(json);
复制代码

这个方法先是调用了父类JsonAdapter的fromJson方法

public abstract  T fromJson(JsonReader reader) throws IOException;
public final T fromJson(BufferedSource source) throws IOException {
return fromJson(JsonReader.of(source));
}
public final T fromJson(String string) throws IOException {
JsonReader reader = JsonReader.of(new Buffer().writeUtf8(string));
T result
= fromJson(reader);
return result;

复制代码

我们发现fromJson是个重载方法,既可以传String也可以传BufferedSource,不过最终调用的都是fromJson(JsonReader reader)这个方法,BufferedSource是Okio的一个类,因为Moshi底层的IO采用的是Okio,但是我们发现参数为JsonReader的这个方法是抽象方法,所以具体的实现是是在ClassJsonAdapter里面,。

@Override public T fromJson(JsonReader reader) throws IOException {
T result
= classFactory.newInstance();
try {
reader
.beginObject();
while (reader.hasNext()) {
int index = reader.selectName(options);
//如果不是Key,直接跳过
if (index == -1) {
reader
.skipName();
reader
.skipValue();
continue;
}
//解析赋值
fieldsArray
[index].read(reader, result);
}
reader
.endObject();
return result;
} catch (IllegalAccessException e) {
throw new AssertionError();
}
}

//递归调用,直到最后
void read(JsonReader reader, Object value) throws IOException, IllegalAccessException {
T fieldValue
= adapter.fromJson(reader);
field
.set(value, fieldValue);
}
复制代码

toJson

String cofigJson = jsonAdapter.toJson(cofig);
复制代码

跟fromJson一样,先是调用的JsonAdapter的toJson方法

public abstract void toJson(JsonWriter writer,  T value) throws IOException;
public final void toJson(BufferedSink sink, T value) throws IOException {
JsonWriter writer = JsonWriter.of(sink);
toJson
(writer, value);
}
public final String toJson( T value) {
Buffer buffer = new Buffer();
try {
toJson
(buffer, value);
} catch (IOException e) {
throw new AssertionError(e); // No I/O writing to a Buffer.
}
return buffer.readUtf8();
}
复制代码

不管传入的是泛型T还是BufferedSink,最终调用的toJson(JsonWriter writer),然后返回了buffer.readUtf8()。我们继续看一下子类的具体实现

@Override public void toJson(JsonWriter writer, T value) throws IOException {
try {
writer
.beginObject();
for (FieldBinding<?> fieldBinding : fieldsArray) {
writer
.name(fieldBinding.name);
//将fieldsArray的值依次写入writer里面
fieldBinding
.write(writer, value);
}
writer
.endObject();
} catch (IllegalAccessException e) {
throw new AssertionError();
}
}
复制代码

Codegen

Moshis Kotlin codegen support is an annotation processor. It generates a small and fast adapter for each of your Kotlin classes at compile time. Enable it by annotating each class that you want to encode as JSON:

所谓Codegen,也就是我们上文提到的Annotation,在编译期间生成对应的JsonAdapter,我们看一下先加一下注解,看看Kotlin帮我们自动生成的注解跟我们自定义的注解有什么区别,rebuild一下项目:

CustomType

@JsonClass(generateAdapter = true)
data
class CustomType(var type: Int, var content: String)
复制代码

我们来看一下对应生成的JsonAdapter

CustomTypeJsonAdapter

这个类方法很多,我们重点看一下formJson跟toJson

override fun fromJson(reader: JsonReader): CustomType {
var type: Int? = null
var content: String? = null
reader
.beginObject()
while (reader.hasNext()) {
when (reader.selectName(options)) {
//按照变量的定义顺序依次赋值
0 -> type = intAdapter.fromJson(reader)
1 -> content = stringAdapter.fromJson(reader)
-1 -> {
reader
.skipName()
reader
.skipValue()
}
}
}
reader
.endObject()
//不通过反射,直接创建对象,传入解析的Value
var result = CustomType(type = type ,content = content )
return result
}

override fun toJson(writer: JsonWriter, value: CustomType?) {
writer
.beginObject()
writer
.name("type")//写入type
intAdapter
.toJson(writer, value.type)
writer
.name("content")//写入content
stringAdapter
.toJson(writer, value.content)
writer
.endObject()
}
复制代码

ConfigBean

@JsonClass(generateAdapter = true)
data
class ConfigBean(var isGood: Boolean ,var title: String ,var type: CustomType)
复制代码

ConfigBeanJsonAdapter

override fun fromJson(reader: JsonReader): ConfigBean {
var isGood: Boolean? = null
var title: String? = null
var type: CustomType? = null
reader
.beginObject()
while (reader.hasNext()) {
when (reader.selectName(options)) {
0 -> isGood = booleanAdapter.fromJson(reader)
1 -> title = stringAdapter.fromJson(reader)
2 -> type = customTypeAdapter.fromJson(reader)
-1 -> {
reader
.skipName()
reader
.skipValue()
}
}
}
reader
.endObject()
var result = ConfigBean(isGood = isGood ,title = title ,type = type
return result
}

override fun toJson(writer: JsonWriter, value: ConfigBean?) {
writer
.beginObject()
writer
.name("isGood")
booleanAdapter
.toJson(writer, value.isGood)
writer
.name("title")
stringAdapter
.toJson(writer, value.title)
writer
.name("type")
customTypeAdapter
.toJson(writer, value.type)
writer
.endObject()
}
复制代码

通过查看生成的CustomTypeJsonAdapter以及ConfigBeanJsonAdapter,我们发现通过Codegen生成也就是注解的方式,跟反射对比一下,会发现有如下优点:

  • 效率高:直接创建对象,无需反射
  • APK体积小:无需引入Kotlin-reflect的Jar包

注意事项

在进行kotlin解析的时候不管是采用Reflect还是Codegen,都必须保证类型一致,也就是父类跟子类必须是Java或者kotlin,因为两种解析方式,最终都是通过ClassType来进行解析的,同时在使用Codegen解析的时候必须保证Koltin的类型是 internal 或者 public 的。

总结

Moshi整个用法跟源码看下来,其实并不是很复杂,但是针对Java跟Kotlin的解析增加了JsonAdapter的转换,以及针对Kotlin的Data类的解析提供了Codegen这种方式,真的让人耳目一新,以前遇到这种业务调用的时候需要二次转换的时候,都是去写 工具 类或者用的时候直接转换。不过Moshi也有些缺点,对于Kotlin的Null类型的支持并不友好,这样会在Kotlin解析的时候如果对于一个不可空的字段变成了Null就会直接抛异常,感觉不太友好,应该给个默认值比较好一些,还有就是对默认值的支持,如果Json出现了Null类型,那么解析到对应的字段依然会被赋值成Null,跟之前的Gson一样,希望以后可以进行完善,毕竟瑕不掩瑜。



本文来源:码农网
本文链接:https://www.codercto.com/a/36525.html

收起阅读 »

Android 快速跳转库

事情起源activity 或者 fragment 每次跳转传值的时候,你是不是都很厌烦那种,参数传递。 那么如果数据极其多的情况下,你的代码将苦不堪言,即使在很好的设计下,也会很蛋疼。那么今天我给大家推荐一个工具 和咱原生跳转进行比较比较:1.跳转方式比较ba...
继续阅读 »

事情起源

activity 或者 fragment 每次跳转传值的时候,你是不是都很厌烦那种,参数传递。 那么如果数据极其多的情况下,你的代码将苦不堪言,即使在很好的设计下,也会很蛋疼。那么今天我给大家推荐一个工具 和咱原生跳转进行比较

比较:

1.跳转方式比较

bash Intenti=new Intent(this,MainActivity.class); 
startActivity(i);

vs

ApMainActivity.newInstance().start(this)
//发送 Intenti=new Intent(this,MainActivity.class);
Bundle bundle = new Bundle();
bundle.putInt("message", "123");
i.putExtra("Bundle", bundle);
startActivity(i);
//接收
String s=bundle.getString("message","");

vs

//发送 
ApMainActivity.newInstance().apply { message = "123" } .start(this)
//接收
AutoJ.inject(this);

实体发送 

//发送 
ApAllDataActivity.newInstance().apply { message = "123" myData = MyData("hfafas",true,21) } .start(this)
//接收
AutoJ.inject(this);

目前 版本号 v1.0.7 更新内容:(专门为kotlin设计的快速跳转工具,如果你的项目只支持java语言请不要用该版本,建议用v1.0.2 地址 Version number v1.0.2) 

1. 代码采用kotlin 语法糖 
2. 支持默认值功能 
3. 不再支持Serializable数据传输,改为性能更好的 Parcelable 大对象传输 
4. 支持多进程activity 跳转 
5. 降低内存占用,可回收内存提升
AutoPage
github地址 https://github.com/smartbackme/AutoPage 
如果觉得不错 github 给个星 Android 容易的跳转工具
注意事项:
必须有如下两个要求
androidx
kotlin & java
支持传输类型
bundle 支持的基本类型都支持(除ShortArray) 以下类型都支持,如果类型不是如下类型,可能会报kapt错误

:Parcelable
String
Long
Int
Boolean
Char
Byte
Float
Double
Short
CharSequence
CharArray
IntArray
LongArray
BooleanArray
DoubleArray
FloatArray
ByteArray
ArrayList
ArrayList<:Parcelable>
Array<:Parcelable>
###使用

project : build.gradle 项目的gradle配置

   buildscript { repositories { maven { url 'https://www.jitpack.io' } } 

在你的每个需要做容易跳转的模块添加如下配置 

3. 你的项目必须要支持 kapt 

4. kotlin kapt 

5. 你的项目必须支持 @Parcelize 注解 也就是必须添加 

apply plugin: 'kotlin-android-extensions'

apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'
android { androidExtensions { experimental = true } }


kapt com.github.smartbackme.AutoPage:autopage-processor:1.0.7
implementation com.github.smartbackme.AutoPage:autopage:1.0.7

重点

  1. @AutoPage 只能在字段或者类上标注
  1. Ap 作为前缀,为你快速跳转

kotlin: 

1. 字段必须标注 @JvmField 和 @AutoPage 

2. onCreate 中 在你的需要跳转的页面加入 AutoJ.inject(this)

java: 

1. 字段必须标注 @AutoPage 

2. onCreate 中 在你的需要跳转的页面加入 AutoJ.inject(this)

### Activity 中使用

例1

简单的跳转

@AutoPage 
class SimpleJump1Activity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_simple_jump1)
}
}

之后调用

ApSimpleJump1Activity.newInstance().start(this)

例2

简单的跳转并且带参数

class MainActivity2 : AppCompatActivity() {


@AutoPage
@JvmField
var message:String? = null

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main2)
AutoJ.inject(this)
findViewById(R.id.text).text = message
}


 之后调用

ApMainActivity2.newInstance().apply { message = "123" } .start(this)

例3:

跳转带有result

@AutoPage class SimpleJumpResultActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) {     super.onCreate(savedInstanceState)     setContentView(R.layout.activitysimplejump_result) }override fun onBackPressed() {
var intent = Intent()
intent.putExtra("message","123")
setResult(RESULT_OK,intent)
super.onBackPressed()
}


之后调用

ApSimpleJumpResultActivity.newInstance().apply { requestCode = 1 }.start(this)


例4:

实体传输

实体 


@Parcelize

data class MyData(var message:String,var hehehe: Boolean,var temp :Int):Parcelable


class AllDataActivity : AppCompatActivity() {


@AutoPage
@JvmField
var myData:MyData? = null
@AutoPage
@JvmField
var message:String? = "this is default value"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

setContentView(R.layout.activity_all_data)
AutoJ.inject(this)


Toast.makeText(this,myData?.toString()+message,Toast.LENGTH_LONG).show()
}


之后调用

ApAllDataActivity.newInstance().apply { message = "123" myData = MyData("hfafas",true,21)


例5:

默认值

class DefaultValueActivity : AppCompatActivity() {


@AutoPage
@JvmField
var message:String? = "this is default value"

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_default_value)
AutoJ.inject(this)

    // var args = intent.getParcelableExtra("123")

    findViewById(R.id.button6).text = message

}

}


之后调用

ApDefaultValueActivity.newInstance().apply { } .start(this)


# 在 fragment 中使用

class FragmentSimpleFragment : Fragment() {


@AutoPage
@JvmField
var message:String? = null

companion object {
fun newInstance() = FragmentSimpleFragment()
}

private lateinit var viewModel: SimpleViewModel

override fun onCreateView(
inflater:
LayoutInflater, container: ViewGroup?,
savedInstanceState:
Bundle?
)
: View {
return inflater.inflate(R.layout.simple_fragment, container, false)
}

override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
AutoJ.inject(this)
viewModel = ViewModelProvider(this).get(SimpleViewModel::class.java)
view?.findViewById(R.id.message)?.text = message

}

}


之后调用

ApFragmentSimpleFragment.newInstance().apply { message = "123" }.build()

下载地址:AutoPage-master.zip

收起阅读 »

快速搭建Android项目-QMUI_Android

QMUI Android 的设计目的是用于辅助快速搭建一个具备基本设计还原效果的 Android 项目,同时利用自身提供的丰富控件及兼容处理,让开发者能专注于业务需求而无需耗费精力在基础代码的设计上。不管是新项目的创建,或是已有项目的维护,均可使开发效率和项目...
继续阅读 »

QMUI Android 的设计目的是用于辅助快速搭建一个具备基本设计还原效果的 Android 项目,同时利用自身提供的丰富控件及兼容处理,让开发者能专注于业务需求而无需耗费精力在基础代码的设计上。不管是新项目的创建,或是已有项目的维护,均可使开发效率和项目质量得到大幅度提升。

功能特性

全局 UI 配置

只需要修改一份配置表就可以调整 App 的全局样式,包括组件颜色、导航栏、对话框、列表等。一处修改,全局生效。

丰富的 UI 控件

提供丰富常用的 UI 控件,例如 BottomSheet、Tab、圆角 ImageView、下拉刷新等,使用方便灵活,并且支持自定义控件的样式。

高效的工具方法

提供高效的工具方法,包括设备信息、屏幕信息、键盘管理、状态栏管理等,可以解决各种常见场景并大幅度提升开发效率。

开始使用

qmui

1. 引入库

最新的库会上传到 JCenter 仓库上,请确保配置了 JCenter 仓库源,然后直接引用:

implementation 'com.qmuiteam:qmui:2.0.0-alpha10'
至此,QMUI 已被引入项目中。

2. 配置主题

把项目的 theme 的 parent 指向 QMUI.Compat,至此,QMUI 可以正常工作。

3. 覆盖组件的默认表现

你可以通过在项目中的 theme 中用 <item name="(name)">(value)</item> 的形式来覆盖 QMUI 组件的默认表现。具体可指定的属性名请参考 @style/QMUI.Compat 或 @style/QMUI 中的属性。

arch

1. 引入库

最新的库会上传到 JCenter 仓库上,请确保配置了 JCenter 仓库源,然后直接引用:

def qmui_arch_version = '2.0.0-alpha10'
implementation "com.qmuiteam:arch:$qmui_arch_version"
kapt "com.qmuiteam:arch-compiler:$qmui_arch_version" // use annotationProcessor if java

2. 在 Application 里初始化

override fun onCreate() {
super.onCreate()
QMUISwipeBackActivityManager.init(this)
}

然后就可以使用 arch 库提供的 QMUIFragment、QMUIFragmentActivity、QMUIActivity 来作为基础类构建自己的界面了。

3. proguard

-keep class **_FragmentFinder { *; }
-keep class androidx.fragment.app.* { *; }

-keep class com.qmuiteam.qmui.arch.record.RecordIdClassMap { *; }
-keep class com.qmuiteam.qmui.arch.record.RecordIdClassMapImpl { *; }

-keep class com.qmuiteam.qmui.arch.scheme.SchemeMap {*;}
-keep class com.qmuiteam.qmui.arch.scheme.SchemeMapImpl {*;}

代码下载:QMUI_Android-master.zip

原文链接:https://github.com/Tencent/QMUI_Android


收起阅读 »

高度封装的 WebView-AgentWeb

AgentWeb 介绍AgentWeb 是一个基于的 Android WebView ,极度容易使用以及功能强大的库,提供了 Android WebView 一系列的问题解决方案 ,并且轻量和极度灵活,体验请下载的agentweb.apk,或者你也可以到 Go...
继续阅读 »

AgentWeb 介绍

AgentWeb 是一个基于的 Android WebView ,极度容易使用以及功能强大的库,提供了 Android WebView 一系列的问题解决方案 ,并且轻量和极度灵活,体验请下载的
agentweb.apk
或者你也可以到 Google Play 里面下载 AgentWeb
详细使用请参照上面的 Sample 。

引入

  • Gradle

     implementation 'com.just.agentweb4.1.4' // (必选)
    implementation 'com.just.agentweb4.1.4'// (可选)
    implementation 'com.download.library4.1.4'// (可选)
  • androidx

     implementation 'com.just.agentweb4.1.4' // (必选)
    implementation 'com.just.agentweb4.1.4'// (可选)
    implementation 'com.download.library4.1.4'// (可选


  • 调用 Javascript 方法拼接太麻烦 ? 请看 。

function callByAndroid(){
console.log("callByAndroid")
}
mAgentWeb.getJsAccessEntrace().quickCallJs("callByAndroid");
  • Javascript 调 Java ?

mAgentWeb.getJsInterfaceHolder().addJavaObject("android",new AndroidInterface(mAgentWeb,this));
window.android.callAndroid();
  • 事件处理

    @Override
public boolean onKeyDown(int keyCode, KeyEvent event) {

if (mAgentWeb.handleKeyEvent(keyCode, event)) {
return true;
}
return super.onKeyDown(keyCode, event);
}
  • 跟随 Activity Or Fragment 生命周期 , 释放 CPU 更省电 。

    @Override
protected void onPause() {
mAgentWeb.getWebLifeCycle().onPause();
super.onPause();

}

@Override
protected void onResume() {
mAgentWeb.getWebLifeCycle().onResume();
super.onResume();
}
@Override
public void onDestroyView() {
mAgentWeb.getWebLifeCycle().onDestroy();
super.onDestroyView();
}
  • 全屏视频播放


android:hardwareAccelerated="true"
android:configChanges="orientation|screenSize"
  • 定位


<!--AgentWeb 是默认允许定位的 ,如果你需要该功能 , 请在你的 AndroidManifest 文件里面加入如下权限 。-->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
  • WebChromeClient 与 WebViewClient

AgentWeb.with(this)
.setAgentWebParent(mLinearLayout,new LinearLayout.LayoutParams(-1,-1) )
.useDefaultIndicator()
.setReceivedTitleCallback(mCallback)
.setWebChromeClient(mWebChromeClient)
.setWebViewClient(mWebViewClient)
.setSecutityType(AgentWeb.SecurityType.strict)
.createAgentWeb()
.ready()
.go(getUrl());
private WebViewClient mWebViewClient=new WebViewClient(){
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
//do you work
}
};
private WebChromeClient mWebChromeClient=new WebChromeClient(){
@Override
public void onProgressChanged(WebView view, int newProgress) {
//do you work
}
};
  • 返回上一页

if (!mAgentWeb.back()){
AgentWebFragment.this.getActivity().finish();
}
  • 获取 WebView

	mAgentWeb.getWebCreator().getWebView();
  • 查看 Cookies

String cookies=AgentWebConfig.getCookiesByUrl(targetUrl);
  • 同步 Cookie

AgentWebConfig.syncCookie("http://www.jd.com","ID=XXXX");
  • MiddlewareWebChromeBase 支持多个 WebChromeClient

//略,请查看 Sample
  • MiddlewareWebClientBase 支持多个 WebViewClient

//略,请查看 Sample
  • 清空缓存

AgentWebConfig.clearDiskCache(this.getContext());
  • 权限拦截

protected PermissionInterceptor mPermissionInterceptor = new PermissionInterceptor() {

@Override
public boolean intercept(String url, String[] permissions, String action) {
Log.i(TAG, "url:" + url + " permission:" + permissions + " action:" + action);
return false;
}
};
  • AgentWeb 完整用法

 //略,请查看 Sample
  • AgentWeb 所需要的权限(在你工程中根据需求选择加入权限)

    <uses-permission android:name="android.permission.INTERNET"></uses-permission>

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"></uses-permission>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"></uses-permission>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"></uses-permission>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"></uses-permission>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"></uses-permission>
<uses-permission android:name="android.permission.READ_PHONE_STATE"></uses-permission>
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"></uses-permission>
<uses-permission android:name="android.permission.CAMERA"></uses-permission>
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"></uses-permission>


  • AgentWeb 所依赖的库

    compile "com.android.support:design:${SUPPORT_LIB_VERSION}" // (3.0.0开始该库可选)
compile "com.android.support:support-v4:${SUPPORT_LIB_VERSION}"
SUPPORT_LIB_VERSION=27.0.2(该值会更新)

混淆

如果你的项目需要加入混淆 , 请加入如下配置

-keep class com.just.agentweb.** {
*;
}
-dontwarn com.just.agentweb.**

Java 注入类不要混淆 , 例如 sample 里面的 AndroidInterface 类 , 需要 Keep 。

-keepclassmembers class com.just.agentweb.sample.common.AndroidInterface{ *; }

注意事项

  • 支付宝使用需要引入支付宝SDK ,并在项目中依赖 , 微信支付不需要做任何操作。
  • AgentWeb 内部使用了 AlertDialog 需要依赖 AppCompat 主题 。
  • setAgentWebParent 不支持 ConstraintLayout 。
  • mAgentWeb.getWebLifeCycle().onPause();会暂停应用内所有WebView 。
  • minSdkVersion 低于等于16以下自定义WebView请注意与 JS 之间通信安全。
  • AgentWeb v3.0.0以上版本更新了包名,混淆的朋友们,请更新你的混淆配置。
  • 多进程无法取消下载,解决方案

代码下载:AgentWeb-master.zip

原文链接:https://github.com/Justson/AgentWeb



收起阅读 »

通用的广告栏控件-ConvenientBanner

demo:ConvenientBanner通用的广告栏控件,让你轻松实现广告头效果。支持无限循环,可以设置自动翻页和时间(而且非常智能,手指触碰则暂停翻页,离开自动开始翻页。你也可以设置在界面onPause的时候不进行自动翻页,onResume之后继续自动翻页...
继续阅读 »

demo:



ConvenientBanner

通用的广告栏控件,让你轻松实现广告头效果。支持无限循环,可以设置自动翻页和时间(而且非常智能,手指触碰则暂停翻页,离开自动开始翻页。你也可以设置在界面onPause的时候不进行自动翻页,onResume之后继续自动翻页),并且提供多种翻页特效。 对比其他广告栏控件,大多都需要对源码进行改动才能加载网络图片,或者帮你集成不是你所需要的图片缓存库。而这个库能让有代码洁癖的你欢喜,不需要对库源码进行修改你就可以使用任何你喜欢的网络图片库进行配合。

demo是用Module方式依赖,你也可以使用gradle 依赖:

    implementation 'com.bigkoo:convenientbanner:2.1.5'//地址变小写了,额。。。
implementation 'androidx.recyclerview:recyclerview:1.0.0+'

// compile 'com.bigkoo:ConvenientBanner:2.1.4'//地址变ConvenientBanner 大写了,额。。。
//compile 'com.bigkoo:convenientbanner:2.0.5'旧版
Config in xml
<com.bigkoo.convenientbanner.ConvenientBanner
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/convenientBanner"
android:layout_width="match_parent"
android:layout_height="200dp"
app:canLoop="true" //控制循环与否
/>

config in java code

//自定义你的Holder,实现更多复杂的界面,不一定是图片翻页,其他任何控件翻页亦可。
convenientBanner.setPages(
new CBViewHolderCreator() {
@Override
public LocalImageHolderView createHolder(View itemView) {
return new LocalImageHolderView(itemView);
}

@Override
public int getLayoutId() {
return R.layout.item_localimage;
}
}, localImages)
//设置两个点图片作为翻页指示器,不设置则没有指示器,可以根据自己需求自行配合自己的指示器,不需要圆点指示器可用不设
// .setPageIndicator(new int[]{R.drawable.ic_page_indicator, R.drawable.ic_page_indicator_focused})
.setOnItemClickListener(this);
//设置指示器的方向
// .setPageIndicatorAlign(ConvenientBanner.PageIndicatorAlign.ALIGN_PARENT_RIGHT)
// .setOnPageChangeListener(this)//监听翻页事件
;

public class LocalImageHolderView implements Holder<Integer>{
private ImageView imageView;
@Override
public View createView(Context context) {
imageView = new ImageView(context);
imageView.setScaleType(ImageView.ScaleType.FIT_XY);
return imageView;
}

@Override
public void UpdateUI(Context context, final int position, Integer data) {
imageView.setImageResource(data);
}
}


原文链接:https://github.com/saiwu-bigkoo/Android-ConvenientBanner

代码下载:Android-ConvenientBanner-master.zip

收起阅读 »

Android图片轮播-banner

使用步骤以下提供的是最简单的步骤,需要复杂的样式自己可以自定义Step 1.依赖bannerGradledependencies{ compile 'com.youth.banner:banner:2.1.0' }Step 2.添加权限到你的 An...
继续阅读 »

使用步骤

以下提供的是最简单的步骤,需要复杂的样式自己可以自定义

Step 1.依赖banner

Gradle

dependencies{
compile 'com.youth.banner:banner:2.1.0'
}

Step 2.添加权限到你的 AndroidManifest.xml


<uses-permission android:name="android.permission.INTERNET" />

Step 3.在布局文件中添加Banner,可以设置自定义属性

!!!此步骤可以省略,可以直接在Activity或者Fragment中new Banner();

<com.youth.banner.Banner
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/banner"
android:layout_width="match_parent"
android:layout_height="高度自己设置" />

Step 4.继承BannerAdapter,和RecyclerView的Adapter一样(如果你只是图片轮播也可以使用默认的)

!!!此步骤可以省略,图片轮播提供有默认适配器,其他的没有提供是因为大家的可变性要求不确定,所以直接自定义的比较好。

/**
* 自定义布局,下面是常见的图片样式,更多实现可以看demo,可以自己随意发挥
*/
public class ImageAdapter extends BannerAdapter<DataBean, ImageAdapter.BannerViewHolder> {

public ImageAdapter(List<DataBean> mDatas) {
//设置数据,也可以调用banner提供的方法,或者自己在adapter中实现
super(mDatas);
}

//创建ViewHolder,可以用viewType这个字段来区分不同的ViewHolder
@Override
public BannerViewHolder onCreateHolder(ViewGroup parent, int viewType) {
ImageView imageView = new ImageView(parent.getContext());
//注意,必须设置为match_parent,这个是viewpager2强制要求的
imageView.setLayoutParams(new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT));
imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
return new BannerViewHolder(imageView);
}

@Override
public void onBindView(BannerViewHolder holder, DataBean data, int position, int size) {
holder.imageView.setImageResource(data.imageRes);
}

class BannerViewHolder extends RecyclerView.ViewHolder {
ImageView imageView;

public BannerViewHolder(@NonNull ImageView view) {
super(view);
this.imageView = view;
}
}
}

Step 5.Banner具体方法调用

public class BannerActivity extends AppCompatActivity {
public void useBanner() {
//--------------------------简单使用-------------------------------
banner.addBannerLifecycleObserver(this)//添加生命周期观察者
.setAdapter(new BannerExampleAdapter(DataBean.getTestData()))
.setIndicator(new CircleIndicator(this));

//—————————————————————————如果你想偷懒,而又只是图片轮播————————————————————————
banner.setAdapter(new BannerImageAdapter<DataBean>(DataBean.getTestData3()) {
@Override
public void onBindView(BannerImageHolder holder, DataBean data, int position, int size) {
//图片加载自己实现
Glide.with(holder.itemView)
.load(data.imageUrl)
.apply(RequestOptions.bitmapTransform(new RoundedCorners(30)))
.into(holder.imageView);
}
})
.addBannerLifecycleObserver(this)//添加生命周期观察者
.setIndicator(new CircleIndicator(this));
//更多使用方法仔细阅读文档,或者查看demo
}
}

Banner使用中优化体验

如果你需要考虑更好的体验,可以看看下面的代码

Step 1.(可选)生命周期改变时

public class BannerActivity {

//方法一:自己控制banner的生命周期

@Override
protected void onStart() {
super.onStart();
//开始轮播
banner.start();
}

@Override
protected void onStop() {
super.onStop();
//停止轮播
banner.stop();
}

@Override
protected void onDestroy() {
super.onDestroy();
//销毁
banner.destroy();
}

//方法二:调用banner的addBannerLifecycleObserver()方法,让banner自己控制

protected void onCreate(Bundle savedInstanceState) {
//添加生命周期观察者
banner.addBannerLifecycleObserver(this);
}
}

常见问题(收录被反复询问的问题)

  • 网络图片加载不出来?

    banner本身不提供图片加载功能,首先确认banner本身使用是否正确,具体参考demo, 然后请检查你的图片加载框架或者网络请求框架,服务端也可能加了https安全认证,是看下是否报有证书相关错误

  • 怎么实现视频轮播?

    demo中有实现类似淘宝商品详情的效果,第一个放视频,后面的放的是图片,并且可以设置首尾不能滑动。 因为大家使用的播放器不一样业务环境也不同,具体情况自己把握,demo就是给一个思路哈!可以参考和修改

  • 我想指定轮播开始的位置?

    现在提供了setStartPosition()方法,在sheAdapter和setDatas直接调用一次就行了,当然setAdapter后通过setCurrentItem设置也行

  • 父控件滑动时,banner切换会获取焦点,然后自动全部显示。不想让banner获取焦点可以给父控件加上:

        //banner也一定要用最新版哦!
    android:focusable="true"
    android:focusableInTouchMode="true"


代码下载:banner-master.zip

原文链接:https://github.com/SenhLinsh/Android-Hot-Libraries

收起阅读 »

多条件筛选菜单-DropDownMenu

示例图:简介一个实用的多条件筛选菜单,在很多App上都能看到这个效果,如美团,爱奇艺电影票等我的博客 自己造轮子--android常用多条件帅选菜单实现思路(类似美团,爱奇艺电影票下拉菜单)特色支持多级菜单你可以完全自定义你的菜单样式,我这里只是封装...
继续阅读 »

示例图:



简介

一个实用的多条件筛选菜单,在很多App上都能看到这个效果,如美团,爱奇艺电影票等

我的博客 自己造轮子--android常用多条件帅选菜单实现思路(类似美团,爱奇艺电影票下拉菜单)

特色

  • 支持多级菜单
  • 你可以完全自定义你的菜单样式,我这里只是封装了一些实用的方法,Tab的切换效果,菜单显示隐藏效果等
  • 并非用popupWindow实现,无卡顿

Gradle Dependency

allprojects {
repositories {
...
maven { url "https://jitpack.io" }
}
}

dependencies {
compile 'com.github.dongjunkun:DropDownMenu:1.0.4'
}

使用

添加DropDownMenu 到你的布局文件,如下

<com.yyydjk.library.DropDownMenu
android:id="@+id/dropDownMenu"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:ddmenuTextSize="13sp" //tab字体大小
app:ddtextUnselectedColor="@color/drop_down_unselected" //tab未选中颜色
app:ddtextSelectedColor="@color/drop_down_selected" //tab选中颜色
app:dddividerColor="@color/gray" //分割线颜色
app:ddunderlineColor="@color/gray" //下划线颜色
app:ddmenuSelectedIcon="@mipmap/drop_down_selected_icon" //tab选中状态图标
app:ddmenuUnselectedIcon="@mipmap/drop_down_unselected_icon"//tab未选中状态图标
app:ddmaskColor="@color/mask_color" //遮罩颜色,一般是半透明
app:ddmenuBackgroundColor="@color/white" //tab 背景颜色
app:ddmenuMenuHeightPercent="0.5" 菜单的最大高度,根据屏幕高度的百分比设置
...
/>

我们只需要在java代码中调用下面的代码

 //tabs 所有标题,popupViews  所有菜单,contentView 内容
mDropDownMenu.setDropDownMenu(tabs, popupViews, contentView);

代码下载:DropDownMenu-master.zip

原文链接:https://github.com/dongjunkun/DropDownMenu

收起阅读 »

安卓选择器类库-AndroidPicker

UI
示例图:AndroidPicker安卓选择器类库,包括日期及时间选择器(可设置范围)、单项选择器(可用于性别、职业、学历、星座等)、城市地址选择器(分省级、地级及县级)、数字选择器(可用于年龄、身高、体重、温度等)、双项选择器、颜色选择器、文件及目录选择器等…...
继续阅读 »

示例图:



AndroidPicker

安卓选择器类库,包括日期及时间选择器(可设置范围)、单项选择器(可用于性别、职业、学历、星座等)、城市地址选择器(分省级、地级及县级)、数字选择器(可用于年龄、身高、体重、温度等)、双项选择器、颜色选择器、文件及目录选择器等……

Install

“app”是测试用例;“library”包括WheelPicker、ColorPicker、FilePicker、MultiplePicker, WheelPicker包括DatePicker、TimePicker、OptionPicker、LinkagePicker、AddressPicker、NumberPicker、DoublePicker等。 其中WheelPicker、FilePicker及ColorPicker是独立的,需要用哪个就只依赖哪个,

 具体步骤如下: 第一步,在项目根目录下的build.gradle里加:

repositories {
maven {
url "https://jitpack.io"
}
}

第二步,在项目的app模块下的build.gradle里加: 滚轮选择器:

dependencies {
implementation 'com.github.gzu-liyujiang.AndroidPicker:WheelPicker:版本号'
}

文件目录选择器:

dependencies {
implementation 'com.github.gzu-liyujiang.AndroidPicker:FilePicker:版本号'
}

颜色选择器:

dependencies {
implementation 'com.github.gzu-liyujiang.AndroidPicker:ColorPicker:版本号'
}

注:Support版本截止1.5.6,从2.0.0开始为AndroidX版本。

Support版本依赖:

dependencies {
implementation 'com.github.gzu-liyujiang.AndroidPicker:WheelPicker:1.5.6.20181018'
}

AndroidX版本依赖:

dependencies {
implementation 'com.github.gzu-liyujiang.AndroidPicker:Common:2.0.0'
implementation 'com.github.gzu-liyujiang.AndroidPicker:WheelPicker:2.0.0'
}

ProGuard

由于地址选择器使用了fastjson来解析,混淆时候需要加入以下类似的规则,不混淆Province、City等实体类。

-keepattributes InnerClasses,Signature
-keepattributes *Annotation*

-keep class cn.qqtheme.framework.entity.** { *;}

Sample (更多用法详见示例项目)

各种设置方法:

picker.setXXX(...);

如:
设置选项偏移量,可用来要设置显示的条目数,范围为1-5,1显示3行、2显示5行、3显示7行……

picker.setOffset(...);

设置启用循环

picker.setCycleDisable(false);

设置每项的高度,范围为2-4

picker.setLineSpaceMultiplier(...);
picker.setItemHeight(...);

设置文字颜色、字号、字体等

picker.setTextColor(...);
picker.setTextSize(...);
picker.setTextPadding(...);
picker.setTextSizeAutoFit(...);
picker.setTypeface(...);

设置单位标签

picker.setLabel(...);
picker.setOnlyShowCenterLabel(...))

设置默认选中项

picker.setSelectedItem(...);
picker.setSelectedIndex(...);

设置滚轮项填充宽度,分割线最长

picker.setUseWeight(true);
picker.setDividerRatio(WheelView.DividerConfig.FILL);

设置触摸弹窗外面是否自动关闭

picker.setCanceledOnTouchOutside(...);

设置分隔线配置项,设置null将隐藏分割线及阴影

picker.setDividerConfig(...);
picker.setDividerColor(...);
picker.setDividerRatio(...);
picker.setDividerVisible(...);

设置内容边距

picker.setContentPadding(...);

设置选中项背景色

picker.setShadowColor(...)

自定义顶部及底部视图

picker.setHeaderView(...);
picker.setFooterView(...);

获得内容视图(不要调用picker.show()方法),可以将其加入到其他容器视图(如自定义的Dialog的视图)中

picker.getContentView();

获得按钮视图(需要先调用picker.show()方法),可以调用该视图相关方法,如setVisibility()

picker.getCancelButton();
picker.getSubmitButton();

自定义选择器示例:

        CustomHeaderAndFooterPicker picker = new CustomHeaderAndFooterPicker(this);
picker.setOnOptionPickListener(new OptionPicker.OnOptionPickListener() {
@Override
public void onOptionPicked(int position, String option) {
showToast(option);
}
});
picker.show();

核心滚轮控件为WheelView,可以参照SinglePicker、DateTimePicker及LinkagePicker自行扩展。


代码下载:AndroidPicker-master.zip

原文链接:https://github.com/gzu-liyujiang/AndroidPicker

收起阅读 »

时间选择器和选项选择器-Android-PickerView

Android-PickerView介绍这是一款仿iOS的PickerView控件,有时间选择器和选项选择器,新版本的详细特性如下:——TimePickerView 时间选择器,支持年月日时分,年月日,年月,时分等格式。——OptionsPickerView ...
继续阅读 »

Android-PickerView

介绍

这是一款仿iOS的PickerView控件,有时间选择器和选项选择器,新版本的详细特性如下:

——TimePickerView 时间选择器,支持年月日时分,年月日,年月,时分等格式。
——OptionsPickerView 选项选择器,支持一,二,三级选项选择,并且可以设置是否联动 。

  • 支持三级联动
  • 设置是否联动
  • 设置循环模式
  • 支持自定义布局。
  • 支持item的分隔线设置。
  • 支持item间距设置。
  • 时间选择器支持起始和终止日期设定。
  • 支持“年,月,日,时,分,秒”,“省,市,区”等选项的单位(label)显示、隐藏和自定义。
  • 支持自定义文字、颜色、文字大小等属性
  • Item的文字长度过长时,文字会自适应缩放到Item的长度,避免显示不完全的问题
  • 支持Dialog 模式。
  • 支持自定义设置容器。
  • 实时回调。

使用注意事项

  • 注意:当我们进行设置时间的启始位置时,需要特别注意月份的设定
  • 原因:Calendar组件内部的月份,是从0开始的,即0-11代表1-12月份
  • 错误使用案例: startDate.set(2013,1,1);  endDate.set(2020,12,1);
  • 正确使用案例: startDate.set(2013,0,1);  endDate.set(2020,11,1);

如何使用:

Android-PickerView 库使用示例:

1.添加Jcenter仓库 Gradle依赖:

compile 'com.contrarywind:Android-PickerView:4.1.9'


或者

Maven


com.contrarywind
Android-PickerView
4.1.9
pom


2.在项目中添加如下代码:

//时间选择器
TimePickerView pvTime = new TimePickerBuilder(MainActivity.this, new OnTimeSelectListener() {
@Override
public void onTimeSelect(Date date, View v) {
Toast.makeText(MainActivity.this, getTime(date), Toast.LENGTH_SHORT).show();
}
}).build();

//条件选择器
OptionsPickerView pvOptions = new OptionsPickerBuilder(MainActivity.this, new OnOptionsSelectListener() {
@Override
public void onOptionsSelect(int options1, int option2, int options3 ,View v) {
//返回的分别是三个级别的选中位置
String tx = options1Items.get(options1).getPickerViewText()
+ options2Items.get(options1).get(option2)
+ options3Items.get(options1).get(option2).get(options3).getPickerViewText();
tvOptions.setText(tx);
}
}).build();
pvOptions.setPicker(options1Items, options2Items, options3Items);
pvOptions.show();


大功告成~

3.如果默认样式不符合你的口味,可以自定义各种属性:

Calendar selectedDate = Calendar.getInstance();
Calendar startDate = Calendar.getInstance();
//startDate.set(2013,1,1);
Calendar endDate = Calendar.getInstance();
//endDate.set(2020,1,1);

//正确设置方式 原因:注意事项有说明
startDate.set(2013,0,1);
endDate.set(2020,11,31);

pvTime = new TimePickerBuilder(this, new OnTimeSelectListener() {
@Override
public void onTimeSelect(Date date,View v) {//选中事件回调
tvTime.setText(getTime(date));
}
})
.setType(new boolean[]{true, true, true, true, true, true})// 默认全部显示
.setCancelText("Cancel")//取消按钮文字
.setSubmitText("Sure")//确认按钮文字
.setContentSize(18)//滚轮文字大小
.setTitleSize(20)//标题文字大小
.setTitleText("Title")//标题文字
.setOutSideCancelable(false)//点击屏幕,点在控件外部范围时,是否取消显示
.isCyclic(true)//是否循环滚动
.setTitleColor(Color.BLACK)//标题文字颜色
.setSubmitColor(Color.BLUE)//确定按钮文字颜色
.setCancelColor(Color.BLUE)//取消按钮文字颜色
.setTitleBgColor(0xFF666666)//标题背景颜色 Night mode
.setBgColor(0xFF333333)//滚轮背景颜色 Night mode
.setDate(selectedDate)// 如果不设置的话,默认是系统时间*/
.setRangDate(startDate,endDate)//起始终止年月日设定
.setLabel("年","月","日","时","分","秒")//默认设置为年月日时分秒
.isCenterLabel(false) //是否只显示中间选中项的label文字,false则每项item全部都带有label。
.isDialog(true)//是否显示为对话框样式
.build();
pvOptions = new OptionsPickerBuilder(this, new OptionsPickerView.OnOptionsSelectListener() {
@Override
public void onOptionsSelect(int options1, int option2, int options3 ,View v) {
//返回的分别是三个级别的选中位置
String tx = options1Items.get(options1).getPickerViewText()
+ options2Items.get(options1).get(option2)
+ options3Items.get(options1).get(option2).get(options3).getPickerViewText();
tvOptions.setText(tx);
}
}) .setOptionsSelectChangeListener(new OnOptionsSelectChangeListener() {
@Override
public void onOptionsSelectChanged(int options1, int options2, int options3) {
String str = "options1: " + options1 + "\noptions2: " + options2 + "\noptions3: " + options3;
Toast.makeText(MainActivity.this, str, Toast.LENGTH_SHORT).show();
}
})
.setSubmitText("确定")//确定按钮文字
.setCancelText("取消")//取消按钮文字
.setTitleText("城市选择")//标题
.setSubCalSize(18)//确定和取消文字大小
.setTitleSize(20)//标题文字大小
.setTitleColor(Color.BLACK)//标题文字颜色
.setSubmitColor(Color.BLUE)//确定按钮文字颜色
.setCancelColor(Color.BLUE)//取消按钮文字颜色
.setTitleBgColor(0xFF333333)//标题背景颜色 Night mode
.setBgColor(0xFF000000)//滚轮背景颜色 Night mode
.setContentTextSize(18)//滚轮文字大小
.setLinkage(false)//设置是否联动,默认true
.setLabels("省", "市", "区")//设置选择的三级单位
.isCenterLabel(false) //是否只显示中间选中项的label文字,false则每项item全部都带有label。
.setCyclic(false, false, false)//循环与否
.setSelectOptions(1, 1, 1) //设置默认选中项
.setOutSideCancelable(false)//点击外部dismiss default true
.isDialog(true)//是否显示为对话框样式
.isRestoreItem(true)//切换时是否还原,设置默认选中第一项。
.build();

pvOptions.setPicker(options1Items, options2Items, options3Items);//添加数据源


4.如果需要自定义布局:

// 注意:自定义布局中,id为 optionspicker 或者 timepicker 的布局以及其子控件必须要有,否则会报空指针
// 具体可参考demo 里面的两个自定义布局
pvCustomOptions = new OptionsPickerBuilder(this, new OptionsPickerView.OnOptionsSelectListener() {
@Override
public void onOptionsSelect(int options1, int option2, int options3, View v) {
//返回的分别是三个级别的选中位置
String tx = cardItem.get(options1).getPickerViewText();
btn_CustomOptions.setText(tx);
}
})
.setLayoutRes(R.layout.pickerview_custom_options, new CustomListener() {
@Override
public void customLayout(View v) {
//自定义布局中的控件初始化及事件处理
final TextView tvSubmit = (TextView) v.findViewById(R.id.tv_finish);
final TextView tvAdd = (TextView) v.findViewById(R.id.tv_add);
ImageView ivCancel = (ImageView) v.findViewById(R.id.iv_cancel);
tvSubmit.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
pvCustomOptions.returnData(tvSubmit);
}
});
ivCancel.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
pvCustomOptions.dismiss();
}
});

tvAdd.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
getData();
pvCustomOptions.setPicker(cardItem);
}
});

}
})
.build();
pvCustomOptions.setPicker(cardItem);//添加数据


5.对使用还有疑问的话,可参考demo代码

请戳我查看demo代码

6.若只需要WheelView基础控件自行扩展实现逻辑,可直接添加基础控件库,Gradle 依赖:

compile 'com.contrarywind:wheelview:4.1.0'


WheelView 使用代码示例:

xml布局:

            android:id="@+id/wheelview"
android:layout_width="match_parent"
android:layout_height="wrap_content" />


Java 代码:

WheelView wheelView = findViewById(R.id.wheelview);

wheelView.setCyclic(false);

final List mOptionsItems = new ArrayList<>();
mOptionsItems.add("item0");
mOptionsItems.add("item1");
mOptionsItems.add("item2");

wheelView.setAdapter(new ArrayWheelAdapter(mOptionsItems));
wheelView.setOnItemSelectedListener(new OnItemSelectedListener() {
@Override
public void onItemSelected(int index) {
Toast.makeText(MainActivity.this, "" + mOptionsItems.get(index), Toast.LENGTH_SHORT).show();
}
});


代码下载:Android-PickerView-master.zip

原文链接:https://github.com/Bigkoo/Android-PickerView

收起阅读 »

Android屏幕适配方案-AndroidAutoLayout

AndroidAutoLayoutAndroid屏幕适配方案,直接填写设计图上的像素尺寸即可完成适配。引入Android Studio将autolayout引入dependencies { compile project(':autolayout') ...
继续阅读 »

AndroidAutoLayout

Android屏幕适配方案,直接填写设计图上的像素尺寸即可完成适配。

引入

  • Android Studio

autolayout引入

dependencies {
compile project(':autolayout')
}

也可以直接

dependencies {
compile 'com.zhy:autolayout:1.4.5'
}
  • Eclipse

建议使用As,方便版本更新。实在不行,只有复制粘贴源码了。

用法

第一步:

在你的项目的AndroidManifest中注明你的设计稿的尺寸。

<meta-data android:name="design_width" android:value="768">
meta-data>
<meta-data android:name="design_height" android:value="1280">
meta-data>

第二步:

让你的Activity继承自AutoLayoutActivity.

非常简单的两个步骤,你就可以开始愉快的编写布局了,详细可以参考sample。

其他用法

如果你不希望继承AutoLayoutActivity,可以在编写布局文件时,将

  • LinearLayout -> AutoLinearLayout
  • RelativeLayout -> AutoRelativeLayout
  • FrameLayout -> AutoFrameLayout

这样也可以完成适配。

目前支持属性

  • layout_width
  • layout_height
  • layout_margin(left,top,right,bottom)
  • pading(left,top,right,bottom)
  • textSize
  • maxWidth, minWidth, maxHeight, minHeight

配置

默认使用的高度是设备的可用高度,也就是不包括状态栏和底部的操作栏的,如果你希望拿设备的物理高度进行百分比化:

可以在Application的onCreate方法中进行设置:

public class UseDeviceSizeApplication extends Application
{
@Override
public void onCreate()
{
super.onCreate();
AutoLayoutConifg.getInstance().useDeviceSize();
}
}

扩展

对于其他继承系统的FrameLayout、LinearLayout、RelativeLayout的控件,比如CardView,如果希望再其内部直接支持"px"百分比化,可以自己扩展,扩展方式为下面的代码,也可参考issue#21

package com.zhy.sample.view;

import android.content.Context;
import android.support.v7.widget.CardView;
import android.util.AttributeSet;

import com.zhy.autolayout.AutoFrameLayout;
import com.zhy.autolayout.utils.AutoLayoutHelper;

/**
* Created by zhy on 15/12/8.
*/

public class AutoCardView extends CardView
{
private final AutoLayoutHelper mHelper = new AutoLayoutHelper(this);

public AutoCardView(Context context)
{
super(context);
}

public AutoCardView(Context context, AttributeSet attrs)
{
super(context, attrs);
}

public AutoCardView(Context context, AttributeSet attrs, int defStyleAttr)
{
super(context, attrs, defStyleAttr);
}

@Override
public AutoFrameLayout.LayoutParams generateLayoutParams(AttributeSet attrs)
{
return new AutoFrameLayout.LayoutParams(getContext(), attrs);
}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
if (!isInEditMode())
{
mHelper.adjustChildren();
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}


}

注意事项

ListView、RecyclerView类的Item的适配

sample中包含ListView、RecyclerView例子,具体查看sample

  • 对于ListView

对于ListView这类控件的item,默认根局部写“px”进行适配是无效的,因为外层非AutoXXXLayout,而是ListView。但是,不用怕,一行代码就可以支持了:

@Override
public View getView(int position, View convertView, ViewGroup parent)
{
ViewHolder holder = null;
if (convertView == null)
{
holder = new ViewHolder();
convertView = LayoutInflater.from(mContext).inflate(R.layout.list_item, parent, false);
convertView.setTag(holder);
//对于listview,注意添加这一行,即可在item上使用高度
AutoUtils.autoSize(convertView);
} else
{
holder = (ViewHolder) convertView.getTag();
}

return convertView;
}

注意 AutoUtils.autoSize(convertView);这行代码的位置即可。demo中也有相关实例。

  • 对于RecyclerView
public ViewHolder(View itemView)
{
super(itemView);
AutoUtils.autoSize(itemView);
}

//...
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType)
{
View convertView = LayoutInflater.from(mContext).inflate(R.layout.recyclerview_item, parent, false);
return new ViewHolder(convertView);
}

一定要记得LayoutInflater.from(mContext).inflate使用三个参数的方法!

指定设置的值参考宽度或者高度

由于该库的特点,布局文件中宽高上的1px是不相等的,于是如果需要宽高保持一致的情况,布局中使用属性:

app:layout_auto_basewidth="height",代表height上编写的像素值参考宽度。

app:layout_auto_baseheight="width",代表width上编写的像素值参考高度。

如果需要指定多个值参考宽度即:

app:layout_auto_basewidth="height|padding"

用|隔开,类似gravity的用法,取值为:

  • width,height
  • margin,marginLeft,marginTop,marginRight,marginBottom
  • padding,paddingLeft,paddingTop,paddingRight,paddingBottom
  • textSize.

TextView的高度问题

设计稿一般只会标识一个字体的大小,比如你设置textSize="20px",实际上TextView所占据的高度肯定大于20px,字的上下都会有一定的间隙,所以一定要灵活去写字体的高度,比如对于text上下的margin可以选择尽可能小一点。或者选择别的约束条件去定位(比如上例,选择了marginBottom)

常见问题

###(1)导入后出现org/gradle/api/publication/maven/internal/DefaultMavenFactory

最简单的方式,通过compile 'com.zhy:autolayout:x.x.x'进行依赖使用,

###(2)RadioGroup,Toolbar等控件中的子View无法完成适配

这个其实上文已经提到过了,需要自己扩展。不过这个很多使用者贡献了他们的扩展类可以直接使用, 参考autolayout-widget, 如果没有发现你需要的容器类,那么你就真的需要自行扩展了,当然如果你完成了扩展,可以给我发个PR,或者让我知道,我可以加入到 autolayout-widget中方便他人,ps:需要用到哪个copy就好了,不要直接引用autolayout-widget,因为其引用了大量的库,可能很多 库你是用不到的。

###(3)java.lang.IllegalStateException: You need to use a Theme.AppCompat theme (or descendant) with this activity.

这个问题是因为默认AutoLayoutActivity会继承自AppCompatActivity,所以默认需要设置 Theme.AppCompat的theme;

如果你使用的依旧是FragmentActivity等,且不考虑使用AppCompatActivity, 你可以选择自己编写一个MyAutoLayoutActivity extends 目前你使用的Activity基类,例如 MyAutoLayoutActivity extends FragmentActivity,然后将该库中AutoLayoutActivity中的逻辑 拷贝进去即可,以后你就继承你的MyAutoLayoutActivity就好了。

ps:还是建议尽快更新SDK版本使用AppCompatActivity.


代码下载:AndroidAutoLayout-master.zip

原文链接:https://github.com/hongyangAndroid/AndroidAutoLayout

收起阅读 »

一个Android TabLayout库,目前有3个TabLayout

FlycoTabLayout示例图:一个Android TabLayout库,目前有3个TabLayoutSlidingTabLayout:参照PagerSlidingTabStrip进行大量修改.新增部分属性新增支持多种Indicator显示器新增支持未读消...
继续阅读 »

FlycoTabLayout


示例图:



一个Android TabLayout库,目前有3个TabLayout

  • SlidingTabLayout:参照PagerSlidingTabStrip进行大量修改.

    • 新增部分属性
    • 新增支持多种Indicator显示器
    • 新增支持未读消息显示
    • 新增方法for懒癌患者
        /** 关联ViewPager,用于不想在ViewPager适配器中设置titles数据的情况 */
    public void setViewPager(ViewPager vp, String[] titles)

    /** 关联ViewPager,用于连适配器都不想自己实例化的情况 */
    public void setViewPager(ViewPager vp, String[] titles, FragmentActivity fa, ArrayList<Fragment> fragments)
  • CommonTabLayout:不同于SlidingTabLayout对ViewPager依赖,它是一个不依赖ViewPager可以与其他控件自由搭配使用的TabLayout.

    • 支持多种Indicator显示器,以及Indicator动画
    • 支持未读消息显示
    • 支持Icon以及Icon位置
    • 新增方法for懒癌患者
        /** 关联数据支持同时切换fragments */
    public void setTabData(ArrayList<CustomTabEntity> tabEntitys, FragmentManager fm, int containerViewId, ArrayList<Fragment> fragments)
  • SegmentTabLayout


Gradle

dependencies{
compile 'com.android.support:support-v4:23.1.1'
compile 'com.nineoldandroids:library:2.4.0'
compile 'com.flyco.roundview:FlycoRoundView_Lib:1.1.2@aar'
compile 'com.flyco.tablayout:FlycoTabLayout_Lib:1.5.0@aar'
}

After v2.0.0(support 2.2+)
dependencies{
compile 'com.android.support:support-v4:23.1.1'
compile 'com.nineoldandroids:library:2.4.0'
compile 'com.flyco.tablayout:FlycoTabLayout_Lib:2.0.0@aar'
}

After v2.0.2(support 3.0+)
dependencies{
compile 'com.android.support:support-v4:23.1.1'
compile 'com.flyco.tablayout:FlycoTabLayout_Lib:2.1.2@aar'
}

代码下载:FlycoTabLayout-master.zip

原文链接:https://github.com/H07000223/FlycoTabLayout

收起阅读 »

FloatWindow 安卓任意界面悬浮窗

效果图:特性:1.支持拖动,提供自动贴边等动画2.内部自动进行权限申请操作3.可自由指定要显示悬浮窗的界面4.应用退到后台时,悬浮窗会自动隐藏5.除小米外,4.4~7.0 无需权限申请6.位置及宽高可设置百分比值,轻松适配各分辨率7.支持权限申请结果、位置等状...
继续阅读 »

效果图:



特性:

1.支持拖动,提供自动贴边等动画

2.内部自动进行权限申请操作

3.可自由指定要显示悬浮窗的界面

4.应用退到后台时,悬浮窗会自动隐藏

5.除小米外,4.4~7.0 无需权限申请

6.位置及宽高可设置百分比值,轻松适配各分辨率

7.支持权限申请结果、位置等状态监听

8.链式调用,简洁清爽

集成:

第 1 步、在工程的 build.gradle 中添加:

	allprojects {
repositories {
...
maven { url 'https://jitpack.io' }
}
}

第 2 步、在应用的 build.gradle 中添加:

	dependencies {
compile 'com.github.yhaolpz:FloatWindow:1.0.9'
}

使用:

0.声明权限

     <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />

1.基础使用

        FloatWindow
.with(getApplicationContext())
.setView(view)
.setWidth(100) //设置控件宽高
.setHeight(Screen.width,0.2f)
.setX(100) //设置控件初始位置
.setY(Screen.height,0.3f)
.setDesktopShow(true) //桌面显示
.setViewStateListener(mViewStateListener) //监听悬浮控件状态改变
.setPermissionListener(mPermissionListener) //监听权限申请结果
.build();

宽高及位置可设像素值或屏幕宽/高百分比,默认宽高为 wrap_content;默认位置为屏幕左上角,x、y 为偏移量。

2.指定界面显示

              .setFilter(true, A_Activity.class, C_Activity.class)

此方法表示 A_Activity、C_Activity 显示悬浮窗,其他界面隐藏。

              .setFilter(false, B_Activity.class)

此方法表示 B_Activity 隐藏悬浮窗,其他界面显示。

注意:setFilter 方法参数可以识别该 Activity 的子类

也就是说,如果 A_Activity、C_Activity 继承自 BaseActivity,你可以这样设置:

              .setFilter(true, BaseActivity.class)

3.可拖动悬浮窗及回弹动画

              .setMoveType(MoveType.slide)
.setMoveStyle(500, new AccelerateInterpolator()) //贴边动画时长为500ms,加速插值器

共提供 4 种 MoveType :

MoveType.slide : 可拖动,释放后自动贴边 (默认)

MoveType.back : 可拖动,释放后自动回到原位置

MoveType.active : 可拖动

MoveType.inactive : 不可拖动

setMoveStyle 方法可设置动画效果,只在 MoveType.slide 或 MoveType.back 模式下设置此项才有意义。默认减速插值器,默认动画时长为 300ms。

4.后续操作

        //手动控制
FloatWindow.get().show();
FloatWindow.get().hide();

//修改显示位置
FloatWindow.get().updateX(100);
FloatWindow.get().updateY(100);

//销毁
FloatWindow.destroy();

以上操作应待悬浮窗初始化后进行。

5.多个悬浮窗

        FloatWindow
.with(getApplicationContext())
.setView(imageView)
.build();

FloatWindow
.with(getApplicationContext())
.setView(button)
.setTag("new")
.build();


FloatWindow.get("new").show();
FloatWindow.get("new").hide();
FloatWindow.destroy("new");

创建第一个悬浮窗不需加 tag,之后再创建就需指定唯一 tag ,以此区分,方便进行后续操作。


代码下载:FloatWindow-master.zip

原文链接:https://github.com/yhaolpz/FloatWindow

收起阅读 »

ImmersionBar -- android 4.4以上沉浸式实现

ImmersionBar -- android 4.4以上沉浸式实现使用android studio// 基础依赖包,必须要依赖 implementation 'com.gyf.immersionbar:immersionbar:3.0.0' // fragm...
继续阅读 »

ImmersionBar -- android 4.4以上沉浸式实现

使用

android studio

// 基础依赖包,必须要依赖
implementation 'com.gyf.immersionbar:immersionbar:3.0.0'
// fragment快速实现(可选)
implementation 'com.gyf.immersionbar:immersionbar-components:3.0.0'
// kotlin扩展(可选)
implementation 'com.gyf.immersionbar:immersionbar-ktx:3.0.0'

关于使用AndroidX支持库

  • 如果你的项目中使用了AndroidX支持库,请在你的gradle.properties加入如下配置,如果已经配置了,请忽略
       android.useAndroidX=true
    android.enableJetifier=true

关于全面屏与刘海

关于全面屏

在manifest加入如下配置,四选其一,或者都写

① 在manifest的Application节点下加入

   <meta-data 
android:name="android.max_aspect"
android:value="2.4" />

② 在manifest的Application节点中加入

   android:resizeableActivity="true"

③ 在manifest的Application节点中加入

   android:maxAspectRatio="2.4"

④ 升级targetSdkVersion为25以上版本

关于刘海屏

在manifest的Application节点下加入,vivo和oppo没有找到相关配置信息

   
<meta-data
android:name="android.notch_support"
android:value="true"/>

<meta-data
android:name="notch.config"
android:value="portrait|landscape" />


Api详解

  • 基础用法

    ImmersionBar.with(this).init();
  • 高级用法(每个参数的意义)

     ImmersionBar.with(this)
    .transparentStatusBar() //透明状态栏,不写默认透明色
    .transparentNavigationBar() //透明导航栏,不写默认黑色(设置此方法,fullScreen()方法自动为true)
    .transparentBar() //透明状态栏和导航栏,不写默认状态栏为透明色,导航栏为黑色(设置此方法,fullScreen()方法自动为true)
    .statusBarColor(R.color.colorPrimary) //状态栏颜色,不写默认透明色
    .navigationBarColor(R.color.colorPrimary) //导航栏颜色,不写默认黑色
    .barColor(R.color.colorPrimary) //同时自定义状态栏和导航栏颜色,不写默认状态栏为透明色,导航栏为黑色
    .statusBarAlpha(0.3f) //状态栏透明度,不写默认0.0f
    .navigationBarAlpha(0.4f) //导航栏透明度,不写默认0.0F
    .barAlpha(0.3f) //状态栏和导航栏透明度,不写默认0.0f
    .statusBarDarkFont(true) //状态栏字体是深色,不写默认为亮色
    .navigationBarDarkIcon(true) //导航栏图标是深色,不写默认为亮色
    .autoDarkModeEnable(true) //自动状态栏字体和导航栏图标变色,必须指定状态栏颜色和导航栏颜色才可以自动变色哦
    .autoStatusBarDarkModeEnable(true,0.2f) //自动状态栏字体变色,必须指定状态栏颜色才可以自动变色哦
    .autoNavigationBarDarkModeEnable(true,0.2f) //自动导航栏图标变色,必须指定导航栏颜色才可以自动变色哦
    .flymeOSStatusBarFontColor(R.color.btn3) //修改flyme OS状态栏字体颜色
    .fullScreen(true) //有导航栏的情况下,activity全屏显示,也就是activity最下面被导航栏覆盖,不写默认非全屏
    .hideBar(BarHide.FLAG_HIDE_BAR) //隐藏状态栏或导航栏或两者,不写默认不隐藏
    .addViewSupportTransformColor(toolbar) //设置支持view变色,可以添加多个view,不指定颜色,默认和状态栏同色,还有两个重载方法
    .titleBar(view) //解决状态栏和布局重叠问题,任选其一
    .titleBarMarginTop(view) //解决状态栏和布局重叠问题,任选其一
    .statusBarView(view) //解决状态栏和布局重叠问题,任选其一
    .fitsSystemWindows(true) //解决状态栏和布局重叠问题,任选其一,默认为false,当为true时一定要指定statusBarColor(),不然状态栏为透明色,还有一些重载方法
    .supportActionBar(true) //支持ActionBar使用
    .statusBarColorTransform(R.color.orange) //状态栏变色后的颜色
    .navigationBarColorTransform(R.color.orange) //导航栏变色后的颜色
    .barColorTransform(R.color.orange) //状态栏和导航栏变色后的颜色
    .removeSupportView(toolbar) //移除指定view支持
    .removeSupportAllView() //移除全部view支持
    .navigationBarEnable(true) //是否可以修改导航栏颜色,默认为true
    .navigationBarWithKitkatEnable(true) //是否可以修改安卓4.4和emui3.x手机导航栏颜色,默认为true
    .navigationBarWithEMUI3Enable(true) //是否可以修改emui3.x手机导航栏颜色,默认为true
    .keyboardEnable(true) //解决软键盘与底部输入框冲突问题,默认为false,还有一个重载方法,可以指定软键盘mode
    .keyboardMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE) //单独指定软键盘模式
    .setOnKeyboardListener(new OnKeyboardListener() { //软键盘监听回调,keyboardEnable为true才会回调此方法
    @Override
    public void onKeyboardChange(boolean isPopup, int keyboardHeight) {
    LogUtils.e(isPopup); //isPopup为true,软键盘弹出,为false,软键盘关闭
    }
    })
    .setOnNavigationBarListener(onNavigationBarListener) //导航栏显示隐藏监听,目前只支持华为和小米手机
    .setOnBarListener(OnBarListener) //第一次调用和横竖屏切换都会触发,可以用来做刘海屏遮挡布局控件的问题
    .addTag("tag") //给以上设置的参数打标记
    .getTag("tag") //根据tag获得沉浸式参数
    .reset() //重置所以沉浸式参数
    .init(); //必须调用方可应用以上所配置的参数

在Activity中实现沉浸式

  • java用法

     ImmersionBar.with(this).init();
  • kotlin用法

     immersionBar {
    statusBarColor(R.color.colorPrimary)
    navigationBarColor(R.color.colorPrimary)
    }

在Fragment中实现沉浸式

在Fragment使用ImmersionBar


在Dialog中实现沉浸式,具体实现参考demo

  • ①结合dialogFragment使用,可以参考demo中的BaseDialogFragment这个类

        ImmersionBar.with(this).init();
  • ②其他dialog,关闭dialog的时候必须调用销毁方法

        ImmersionBar.with(this, dialog).init();

    销毁方法:

    java中

        ImmersionBar.destroy(this, dialog);

    kotlin中

        destroyImmersionBar(dialog)


在PopupWindow中实现沉浸式,具体实现参考demo

重点是调用以下方法,但是此方法会导致有导航栏的手机底部布局会被导航栏覆盖,还有底部输入框无法根据软键盘弹出而弹出,具体适配请参考demo。

    popupWindow.setClippingEnabled(false);

状态栏与布局顶部重叠解决方案,六种方案根据不同需求任选其一

  • ① 使用dimen自定义状态栏高度,不建议使用,因为设备状态栏高度并不是固定的

    在values-v19/dimens.xml文件下

        <dimen name="status_bar_height">25dpdimen>

    在values/dimens.xml文件下

        <dimen name="status_bar_height">0dpdimen>

    然后在布局界面添加view标签,高度指定为status_bar_height

       <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/darker_gray"
    android:orientation="vertical">

    <View
    android:layout_width="match_parent"
    android:layout_height="@dimen/status_bar_height"
    android:background="@color/colorPrimary" />

    <android.support.v7.widget.Toolbar
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="@color/colorPrimary"
    app:title="方法一"
    app:titleTextColor="@android:color/white" />
    LinearLayout>
  • ② 使用系统的fitsSystemWindows属性,使用该属性不会导致输入框与软键盘冲突问题,不要再Fragment使用该属性,只适合纯色状态栏

        <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:fitsSystemWindows="true">
    LinearLayout>

    然后使用ImmersionBar时候必须指定状态栏颜色

        ImmersionBar.with(this)
    .statusBarColor(R.color.colorPrimary)
    .init();
    • 注意:ImmersionBar一定要在设置完布局以后使用,
  • ③ 使用ImmersionBar的fitsSystemWindows(boolean fits)方法,只适合纯色状态栏

        ImmersionBar.with(this)
    .fitsSystemWindows(true) //使用该属性,必须指定状态栏颜色
    .statusBarColor(R.color.colorPrimary)
    .init();
  • ④ 使用ImmersionBar的statusBarView(View view)方法,可以用来适配渐变色状态栏、侧滑返回

    在标题栏的上方增加View标签,高度指定为0dp

        <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/darker_gray"
    android:orientation="vertical">

    <View
    android:layout_width="match_parent"
    android:layout_height="0dp"
    android:background="@color/colorPrimary" />

    <android.support.v7.widget.Toolbar
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="@color/colorPrimary"
    app:title="方法四"
    app:titleTextColor="@android:color/white" />
    LinearLayout>

    然后使用ImmersionBar的statusBarView方法,指定view就可以啦

         ImmersionBar.with(this)
    .statusBarView(view)
    .init();
    //或者
    //ImmersionBar.setStatusBarView(this,view);
  • ⑤ 使用ImmersionBar的titleBar(View view)方法,原理是设置paddingTop,可以用来适配渐变色状态栏、侧滑返回

             ImmersionBar.with(this)
    .titleBar(view) //可以为任意view,如果是自定义xml实现标题栏的话,标题栏根节点不能为RelativeLayout或者ConstraintLayout,以及其子类
    .init();
    //或者
    //ImmersionBar.setTitleBar(this, view);
  • ⑥ 使用ImmersionBar的titleBarMarginTop(View view)方法,原理是设置marginTop,只适合纯色状态栏

             ImmersionBar.with(this)
    .titleBarMarginTop(view) //可以为任意view
    .statusBarColor(R.color.colorPrimary) //指定状态栏颜色,根据情况是否设置
    .init();
    //或者使用静态方法设置
    //ImmersionBar.setTitleBarMarginTop(this,view);

解决EditText和软键盘的问题

  • 第一种方案
        ImmersionBar.with(this)
    .keyboardEnable(true) //解决软键盘与底部输入框冲突问题
    // .keyboardEnable(true, WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE
    // | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE) //软键盘自动弹出
    .init();
  • 第二种方案 不使用keyboardEnable方法,只需要在布局的根节点(最外层节点)加上android:fitsSystemWindows="true"属性即可,只适合纯色状态栏

当白色背景状态栏遇到不能改变状态栏字体为深色的设备时,解决方案

      ImmersionBar.with(this)
.statusBarDarkFont(true, 0.2f) //原理:如果当前设备支持状态栏字体变色,会设置状态栏字体为黑色,如果当前设备不支持状态栏字体变色,会使当前状态栏加上透明度,否则不执行透明度
.init();


状态栏和导航栏其它方法

  • public static boolean hasNavigationBar(Activity activity)

    判断是否存在导航栏

  • public static int getNavigationBarHeight(Activity activity)

    获得导航栏的高度

  • public static int getNavigationBarWidth(Activity activity)

    获得导航栏的宽度

  • public static boolean isNavigationAtBottom(Activity activity)

    判断导航栏是否在底部

  • public static int getStatusBarHeight(Activity activity)

    获得状态栏的高度

  • public static int getActionBarHeight(Activity activity)

    获得ActionBar的高度

  • public static boolean hasNotchScreen(Activity activity)

    是否是刘海屏

  • public static boolean getNotchHeight(Activity activity)

    获得刘海屏高度

  • public static boolean isSupportStatusBarDarkFont()

    判断当前设备支不支持状态栏字体设置为黑色

  • public static boolean isSupportNavigationIconDark()

    判断当前设备支不支持导航栏图标设置为黑色

  • public static void hideStatusBar(Window window)

    隐藏状态栏

混淆规则(proguard-rules.pro)

 -keep class com.gyf.immersionbar.* {*;} 
-dontwarn com.gyf.immersionbar.**



代码下载 :ImmersionBar-master.zip

原文链接:https://github.com/gyf-dev/ImmersionBar


收起阅读 »

material风格-DialogUtil

DialogUtilmaterial风格(v7支持包中的),ios风格,自动获取顶层activity,可在任意界面弹出,可在任意线程弹出注意点在activity已经resume后再调用,不要在onstart里用,否则可能会不显示. 如果非要在onstart里,...
继续阅读 »

DialogUtil

material风格(v7支持包中的),ios风格,自动获取顶层activity,可在任意界面弹出,可在任意线程弹出

注意点

  • 在activity已经resume后再调用,不要在onstart里用,否则可能会不显示. 
  • 如果非要在onstart里,就记得调用setActivity()
  • 如果有的国产机不显示,就调用setActivity()
  • 不要滥用loadingdialog:

注意使用的场景:

 第一此进入页面,用layout内部的loadingview,有很多statelayout框架,
再次刷新,用刷新头显示刷新状态
局部刷新或点击某按钮访问网络,用loading dialog,不影响页面本身状态,类似web中的ajax请求.

特性

  • **自动获取顶层activity,**无需传入activity也可弹出dialog.如果传入,则指定在此activity弹出.
  • 安全,任意线程均可调用.
  • 类型丰富,包括常用的ios风格dialog和material design风格的dialog,且按钮和文字样式可便捷地修改
  • 自定义view:可以传入自定义的view,定义好事件,本工具负责安全地显示
  • 也可以保留iso样式或material 样式的底部按钮和上方title(可隐藏),中间的view可以完全自定义
  • 考虑了显示内容超多时的滑动和与屏幕的间隙.
  • 也可以设置宽高百分比来自定义宽高
  • 可以关闭默认的阴影背景,从而能使用xml中自定义的背景(弹出自定义view的dialog时常用)
  • 支持国际化
  • 智能弹出和隐藏软键盘.自定义view的dialog只要设置setNeedSoftKeyboard为true,即可自动处理软键盘的弹出和隐藏
  • ios样式和material 样式的均可以在三种状态下显示: 普通dialog,TYPE_TOAST,作为activity.(原生ProgressDialog和Design包下的BottomSheetDialog除外,其在TYPE_TOAST或activity显示有异样)
  • 支持带x的广告样式的动画

useage

gradle

Step 1. Add the JitPack repository to your build file

Add it in your root build.gradle at the end of repositories:

	allprojects {
repositories {
...
maven { url "https://jitpack.io" }
}
}

Step 2. Add the dependency

	dependencies {
       compile ('com.github.hss01248:DialogUtil:lastest release'){
exclude group: 'com.android.support'
       }
        compile 'com.android.support:appcompat-v7:26.1.0'
compile 'com.android.support:recyclerview-v7:26.1.0'
compile 'com.android.support:design:26.1.0'
//将26.1.0: 改为自己项目中一致的版本
}

lastest release: https://github.com/hss01248/DialogUtil/releases

初始化

//在Application的oncreate方法里:
传入context
StyledDialog.init(this);

activity生命周期callback中拿到顶层activity引用:
registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
@Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
ActivityStackManager.getInstance().addActivity(activity);
}

@Override
public void onActivityStarted(Activity activity) {

}

@Override
public void onActivityResumed(Activity activity) {
}

@Override
public void onActivityPaused(Activity activity) {

}

@Override
public void onActivityStopped(Activity activity) {

}

@Override
public void onActivitySaveInstanceState(Activity activity, Bundle outState) {

}

@Override
public void onActivityDestroyed(Activity activity) {
ActivityStackManager.getInstance().removeActivity(activity);
}
});

示例代码(MainActivity里)

        //使用默认样式时,无须.setxxx:
StyledDialog.buildLoading().show();

//自定义部分样式时:
StyledDialog.buildMdAlert("title", msg, new MyDialogListener() {
@Override
public void onFirst() {
showToast("onFirst");
}

@Override
public void onSecond() {
showToast("onSecond");
}

@Override
public void onThird() {
showToast("onThird");
}


})
.setBtnSize(20)
.setBtnText("i","b","3")
.show();

相关回调

MyDialogListener

	public abstract void onFirst();//md-确定,ios-第一个
public abstract void onSecond();//md-取消,ios-第二个
public void onThird(){}//md-netural,ios-第三个

public void onCancle(){}

/**
* 提供给Input的回调
* @param input1
* @param input2
*/

public void onGetInput(CharSequence input1,CharSequence input2){

}

/**
* 提供给MdSingleChoose的回调
* @param chosen
* @param chosenTxt
*/

public void onGetChoose(int chosen,CharSequence chosenTxt){

}

/**
* 提供给MdMultiChoose的回调
* @param states
*/

public void onChoosen( List selectedIndex, List selectedStrs,boolean[] states){

}

MyItemDialogListener

 /**
* IosSingleChoose,BottomItemDialog的点击条目回调
* @param text
* @param position
*/

public abstract void onItemClick(CharSequence text, int position);


/**
* BottomItemDialog的底部按钮(经常是取消)的点击回调
*/

public void onBottomBtnClick(){}


最后必须调用show(),返回dialog对象

对话框的消失

StyledDialog.dismiss(DialogInterface... dialogs);

两个loading对话框不需要对象就可以直接dismisss:

StyledDialog.dismissLoading();

progress dialog 的进度更新

/**
* 可以在任何线程调用
* @param dialog 传入show方法返回的对象
* @param progress
* @param max
* @param msg 如果是转圈圈,会将msg变成msg:78%的形式.如果是水平,msg不起作用
* @param isHorizontal 是水平线状,还是转圈圈
*/

public static void updateProgress( Dialog dialog, int progress, int max, CharSequence msg, boolean isHorizontal)


代码下载: DialogUtil-master.zip

原文链接:https://github.com/hss01248/DialogUtil

收起阅读 »

仿QQ未读气泡拖拽效果-BGABadgeView-Android

效果图Gradle依赖dependencies { implementation 'cn.bingoogolapple:bga-badgeview-api:latestVersion' annotationProcessor "cn.bingoog...
继续阅读 »

效果图


Gradle依赖

dependencies {
implementation 'cn.bingoogolapple:bga-badgeview-api:latestVersion'
annotationProcessor "cn.bingoogolapple:bga-badgeview-compiler:latestVersion"
}


初始化控件

  1. 在项目任意一个类上面添加 BGABadge 注解,例如新建一个类 BGABadgeInit 专门用于初始化徽章控件
  2. 需要哪些类具有徽章功能,就把那些类的 Class 作为 BGABadge 注解的参数「下面的代码块给出了例子,不需要的可以删掉对应的行」
@BGABadge({
View.class, // 对应 cn.bingoogolapple.badgeview.BGABadgeView,不想用这个类的话就删了这一行
ImageView.class, // 对应 cn.bingoogolapple.badgeview.BGABadgeImageView,不想用这个类的话就删了这一行
TextView.class, // 对应 cn.bingoogolapple.badgeview.BGABadgeFloatingTextView,不想用这个类的话就删了这一行
RadioButton.class, // 对应 cn.bingoogolapple.badgeview.BGABadgeRadioButton,不想用这个类的话就删了这一行
LinearLayout.class, // 对应 cn.bingoogolapple.badgeview.BGABadgeLinearLayout,不想用这个类的话就删了这一行
FrameLayout.class, // 对应 cn.bingoogolapple.badgeview.BGABadgeFrameLayout,不想用这个类的话就删了这一行
RelativeLayout.class, // 对应 cn.bingoogolapple.badgeview.BGABadgeRelativeLayout,不想用这个类的话就删了这一行
FloatingActionButton.class, // 对应 cn.bingoogolapple.badgeview.BGABadgeFloatingActionButton,不想用这个类的话就删了这一行
...
...
...
})
public class BGABadgeInit {
}
  1. 再 AS 中执行 Build => Rebuild Project
  2. 经过前面三个步骤后就可以通过「cn.bingoogolapple.badgeview.BGABadge原始类名」来使用徽章控件了

接口说明

/**
* 显示圆点徽章
*/
void showCirclePointBadge();

/**
* 显示文字徽章
*
* @param badgeText
*/
void showTextBadge(String badgeText);

/**
* 隐藏徽章
*/
void hiddenBadge();

/**
* 显示图像徽章
*
* @param bitmap
*/
void showDrawableBadge(Bitmap bitmap);

/**
* 设置拖动删除徽章的代理
*
* @param delegate
*/
void setDragDismissDelegage(BGADragDismissDelegate delegate);

/**
* 是否显示徽章
*
* @return
*/
boolean isShowBadge();

/**
* 是否可拖动
*
* @return
*/
boolean isDraggable();

/**
* 是否正在拖动
*
* @return
*/
boolean isDragging();


代码下载:BGABadgeView-Android-master.zip

原文链接:https://github.com/bingoogolapple/BGABadgeView-Android

收起阅读 »

图片浏览缩放控件-PhotoView

PhotoView 图片浏览缩放控件一个流畅的photoview#注意 由于facebook的Fresco图片加载组件所加载出来的drawable图片并非真实的drawable,无法直接获取图片真实宽高,也无法直接响应ImageMatrix的变换, 且根据Fr...
继续阅读 »

PhotoView 图片浏览缩放控件

一个流畅的photoview

#注意 由于facebook的Fresco图片加载组件所加载出来的drawable图片并非真实的drawable,无法直接获取图片真实宽高,也无法直接响应ImageMatrix的变换, 且根据Fresco文档的介绍,在后续的版本中,DraweeView会直接继承自View,所有暂不考虑支持Fresco。 对于其他第三方图片加载库如Glide,ImageLoader,xUtils都是支持的

#使用 1.Gradle添加依赖 (推荐)

dependencies {
compile 'com.bm.photoview:library:1.4.1'
}

(或者也可以将项目下载下来,将Info.java和PhotoView.java两个文件拷贝到你的项目中,不推荐)

2.xml添加

 <com.bm.library.PhotoView
android:id="@+id/img"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerInside"
android:src="@drawable/bitmap1" />

3.java代码

PhotoView photoView = (PhotoView) findViewById(R.id.img);
// 启用图片缩放功能
photoView.enable();
// 禁用图片缩放功能 (默认为禁用,会跟普通的ImageView一样,缩放功能需手动调用enable()启用)
photoView.disenable();
// 获取图片信息
Info info = photoView.getInfo();
// 从普通的ImageView中获取Info
Info info = PhotoView.getImageViewInfo(ImageView);
// 从一张图片信息变化到现在的图片,用于图片点击后放大浏览,具体使用可以参照demo的使用
photoView.animaFrom(info);
// 从现在的图片变化到所给定的图片信息,用于图片放大后点击缩小到原来的位置,具体使用可以参照demo的使用
photoView.animaTo(info,new Runnable() {
@Override
public void run() {
//动画完成监听
}
});
// 获取/设置 动画持续时间
photoView.setAnimaDuring(int during);
int d = photoView.getAnimaDuring();
// 获取/设置 最大缩放倍数
photoView.setMaxScale(float maxScale);
float maxScale = photoView.getMaxScale();
// 设置动画的插入器
photoView.setInterpolator(Interpolator interpolator);

代码下载:PhotoView-master.zip

原文链接:https://github.com/bm-x/PhotoView

收起阅读 »

高度自定义、支持周视图的日历控件-CalendarView

CalendarView使用详细文档日历控件定制是移动开发平台上比较常见的而且比较难的需求,一般会遇到以下问题:性能差,加载速度慢,原因是各种基于GridView或RecyclerView等ViewGroup实现的日历,控件数太多,假设一个月视图界面有42个i...
继续阅读 »

CalendarView使用详细文档

日历控件定制是移动开发平台上比较常见的而且比较难的需求,一般会遇到以下问题:

  • 性能差,加载速度慢,原因是各种基于GridView或RecyclerView等ViewGroup实现的日历,控件数太多,假设一个月视图界面有42个item,每个item里面分别就有2个子TextView:天数、农历数和本身3个控件,这样一个月视图就有42 * 3+1(RecyclerView or GridView),清楚ViewPager特性的开发者就会明白,一般ViewPager持有3个item,那么一个日历控件持有的View控件数的数量将达到 1(ViewPager)+ 3(RecyclerView or GridView) + 3 * 42 * 3 = 382,如果用1个View来代替RecyclerView等,用Canvas来代替各种TextView,那View的数量瞬间将下降360+,内存和性能优势将相当明显了
  • 难定制 一般日历框架发布的同时也将UI风格确定下来了,假如人人都使用这个日历框架,那么将会千篇一律,难以突出自己的风格,要么就得改源码,成本太大,不太实际
  • 功能性不足 例如无法自定义周起始、无法更改选择模式、动态设置UI等等
  • 无法满足产品经理提出的变态需求 今天产品经历说我们要这样的实现、明天跟你说这里得改、后天说我们得限制一些日期...

但现在有了全新的 CalendarView 控件,它解锁了各种姿势,而且你可以任意定制,直到你满足为止...

插拔式设计

插拔式设计:好比插座一样,插上灯泡就会亮,插上风扇就会转,看用户需求什么而不是看插座有什么,只要是电器即可。此框架使用插拔式,既可以在编译时指定年月日视图,如:app:month_view="xxx.xxx.MonthView.class",也可在运行时动态更换年月日视图,如:CalendarView.setMonthViewClass(MonthView.Class),从而达到UI即插即用的效果,相当于框架不提供UI实现,让UI都由客户端实现,不至于日历UI都千篇一律,只需遵守插拔式接口即可随意定制,自由化程度非常高。

CalendarView 的特性

  • 基于Canvas绘制,极速性能
  • 热插拔思想,任意定制周视图、月视图,即插即用!
  • 支持单选、多选、范围选择、国内手机日历默认自动选择等选择模式
  • 支持静态、动态设置周起始,一行代码搞定
  • 支持静态、动态设置日历项高度、日历填充模式
  • 支持设置任意日期范围、任意拦截日期
  • 支持多点触控、手指平滑切换过渡,拒绝界面抖动
  • 类NestedScrolling特性,嵌套滚动
  • 既然这么多支持,那一定支持英语、繁体、简体,任意定制实现

注意: 框架本身只是实现各自逻辑,不实现UI,UI如同一张白纸,任凭客户端自行通过Canvas绘制实现,如果不熟悉Canvas的,请自行了解各自Canvas.drawXXX方法,UI都靠Canvas实现,坐标都已经计算好了,因此怎么隐藏农历,怎么换某些日期的字,这些都不属于框架范畴,只要你想换,都能随便换。

再次注意: app Demo只是Demo,只是示例如何使用,与框架本身没有关联,不属于框架一部分

接下来请看CalendarView操作,前方高能

  • 你这样继承自己的月视图和周视图,只需要依次实现绘制选中:onDrawSelected、绘制事务:onDrawScheme、绘制文本:onDrawText 这三个回调即可,参数和坐标都已经在回调函数上实现好,周视图也是一样的逻辑,只是不需要y参数
/**
* 定制高仿魅族日历界面,按你的想象力绘制出各种各样的界面
*
*/
public class MeiZuMonthView extends MonthView {

/**
* 绘制选中的日子
*
* @param canvas canvas
* @param calendar 日历日历calendar
* @param x 日历Card x起点坐标
* @param y 日历Card y起点坐标
* @param hasScheme hasScheme 非标记的日期
* @return 返回true 则绘制onDrawScheme,因为这里背景色不是是互斥的,所以返回true
*/
@Override
protected boolean onDrawSelected(Canvas canvas, Calendar calendar, int x, int y, boolean hasScheme) {
//这里绘制选中的日子样式,看需求需不需要继续调用onDrawScheme
return true;
}

/**
* 绘制标记的事件日子
*
* @param canvas canvas
* @param calendar 日历calendar
* @param x 日历Card x起点坐标
* @param y 日历Card y起点坐标
*/
@Override
protected void onDrawScheme(Canvas canvas, Calendar calendar, int x, int y) {
//这里绘制标记的日期样式,想怎么操作就怎么操作
}

/**
* 绘制文本
*
* @param canvas canvas
* @param calendar 日历calendar
* @param x 日历Card x起点坐标
* @param y 日历Card y起点坐标
* @param hasScheme 是否是标记的日期
* @param isSelected 是否选中
*/
@Override
protected void onDrawText(Canvas canvas, Calendar calendar, int x, int y, boolean hasScheme, boolean isSelected) {
//这里绘制文本,不要再问我怎么隐藏农历了,不要再问我怎么把某个日期换成特殊字符串了,要怎么显示你就在这里怎么画,你不画就不显示,是看你想怎么显示日历的,而不是看框架
}
}
  • 当你实现好之后,直接在xml界面上添加特性,编译后可以即时预览效果:
<attr name="month_view" format="string" />
<attr name="week_view" format="string" />

app:month_view="com.haibin.calendarviewproject.MeiZuMonthView"
app:week_view="com.haibin.calendarviewproject.MeiZuWeekView"
  • 视图有多种模式可供选择,几乎涵盖了各种需求,看各自的需求自行继承
如果继承这2个,MonthView、WeekView,即select_mode="default_mode",这是默认的手机自带的日历模式,会自动选择月的第一天,不支持拦截器,
也可以设置select_mode="single_mode",即单选模式,支持拦截器

如果继承这2个,RangeMonthView、RangeWeekView,即select_mode="range_mode",这是范围选择模式,支持拦截器

如果继承这2个,MultiMonthView、MultiWeekView,即select_mode="multi_mode",这是多选模式,支持拦截器
  • 如果静态模式无法满足你的需求,你可能需要动态变换定制的视图界面,你可以使用热插拔特性,即插即用,不爽就换:
mCalendarView.setWeekView(MeiZuWeekView.class);

mCalendarView.setMonthView(MeiZuMonthView.class);
  • 如果你需要可收缩的日历,你可以在 CalendarView 父布局添加 CalendarLayout,当然你不需要周视图也可以不用,例如原生日历,使用如下:
<com.haibin.calendarview.CalendarLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
app:default_status="shrink"
app:calendar_show_mode="only_week_view"
app:calendar_content_view_id="@+id/recyclerView">

<com.haibin.calendarview.CalendarView
android:id="@+id/calendarView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#fff"
app:month_view="com.haibin.calendarviewproject.simple.SimpleMonthView"
app:week_view="com.haibin.calendarviewproject.simple.SimpleWeekView"
app:week_bar_view="com.haibin.calendarviewproject.EnglishWeekBar"
app:calendar_height="50dp"
app:current_month_text_color="#333333"
app:current_month_lunar_text_color="#CFCFCF"
app:min_year="2004"
app:other_month_text_color="#e1e1e1"
app:scheme_text=""
app:scheme_text_color="#333"
app:scheme_theme_color="#333"
app:selected_text_color="#fff"
app:selected_theme_color="#333"
app:week_start_with="mon"
app:week_background="#fff"
app:month_view_show_mode="mode_only_current"
app:week_text_color="#111" />

<android.support.v7.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#ffffff" />
com.haibin.calendarview.CalendarLayout>
  • 使用可收缩的日历你可以使用监听器,监听视图变换
public void setOnViewChangeListener(OnViewChangeListener listener);
  • 当然 CalendarLayout 有很多特性可提供周月视图无缝切换,而且,平滑手势不抖动!使用 CalendarLayout,你需要指定 calendar_content_view_id,用他来平移收缩月视图,更多特性如下:

<attr name="calendar_show_mode">
<enum name="both_month_week_view" value="0" />
<enum name="only_week_view" value="1" />
<enum name="only_month_view" value="2" />
attr>

<attr name="default_status">
<enum name="expand" value="0" />
<enum name="shrink" value="1" />
attr>

<attr name="calendar_content_view_id" format="integer" />
  • CalendarView 可以设置全屏,只需设置 app:calendar_match_parent="true"即可,全屏CalendarView是不需要周视图的,不必嵌套CalendarLayout

  • CalendarView 也提供了高效便利的年视图,可以快速切换年份、月份,十分便利

  • 但年视图也不一定就适合你的胃口,如果你希望像弹出 DatePickerView,通过它来跳转日期,你可以使用以下的API来让日历与其它控件联动

CalendarView.scrollToCalendar();

CalendarView.scrollToNext();

CalendarView.scrollToPre();

CalendarView.scrollToXXX();

  • 你也许需要像魅族日历一样,可以静态、动态更换周起始

app:week_start_with="mon、sun、sat"

CalendarView.setWeekStarWithSun();

CalendarView.setWeekStarWithMon();

CalendarView.setWeekStarWithSat();

  • 假如你是做酒店、旅游等应用场景的APP的,那么需要可选范围的日历,你可以这样继承,和普通视图实现完全一样
public class CustomRangeMonthView extends RangeMonthView{

}

public class CustomRangeWeekView extends RangeWeekView{

}
  • 然后你需要设置选择模式为范围模式:select_mode="range_mode"

  • 酒店式日历场景当然是不能从昨天开始订房的,也不能无限期订房,所以你需要静态或动态设置日历范围、精确到具体某一天!!!









CalendarView.setRange(int minYear, int minYearMonth, int minYearDay,
int maxYear, int maxYearMonth, int maxYearDay)

  • 当然还有更特殊的日子也是不能选择的,例如:某月某号起这N天时间内因为超强台风来袭,酒店需停止营业N天,这段期间不可订房,这时日期拦截器就排上用场了
//设置日期拦截事件
mCalendarView.setOnCalendarInterceptListener(new CalendarView.OnCalendarInterceptListener() {
@Override
public boolean onCalendarIntercept(Calendar calendar) {
//这里写拦截条件,返回true代表拦截,尽量以最高效的代码执行
return calendar.isWeekend();
}

@Override
public void onCalendarInterceptClick(Calendar calendar, boolean isClick) {
//todo 点击拦截的日期回调
}
});
  • 添加日期拦截器和范围设置后,你可以在周月视图按需求获得他们的结果
boolean isInRange = isInRange(calendar);//日期是否在范围内,超出范围的可以置灰

boolean isEnable = !onCalendarIntercept(calendar);//日期是否可用,没有被拦截,被拦截的可以置灰

  • 假如你是做清单类、任务类APP的,可能会有这样的需求:标记某天事务的进度,这也很简单,因为:日历界面长什么样,你自己说了算!!!

  • 也许你只需要像原生日历那样就够了,但原生日历那奇怪且十分不友好的style,受到theme的影响,各种头疼,使用此控件,你只需要简简单单定制月视图就够了,CalendarView 能非常简单就高仿各种日历UI

  • CalendarView 提供了 setSchemeDate(Map mSchemeDates) 这个十分高效的API用来动态标记事务,即时你的数据量达到数千、数万、数十万,都不会对UI渲染造成影响

  • 日历类 Calendar 提供了许多十分有用的API

boolean isWeekend();//判断是不是周末,可以用不同的画笔绘制周末的样式

int getWeek();//获取星期

String getSolarTerm();//获取24节气,可以用不同颜色标记不同节日

String getGregorianFestival();//获取公历节日,自由判断,把节日换上喜欢的颜色

String getTraditionFestival();//获取传统节日

boolean isLeapYear();//是否是闰年

int getLeapMonth();//获取闰月

boolean isSameMonth(Calendar calendar);//是否相同月

int compareTo(Calendar calendar);//比较日期大小 -1 0 1

long getTimeInMillis();//获取时间戳

int differ(Calendar calendar);//日期运算,相差多少天

CalendarView 的全部xml特性如下:

<declare-styleable name="CalendarView">

<attr name="calendar_padding" format="dimension" />

<attr name="month_view" format="color" />
<attr name="week_view" format="string" />
<attr name="week_bar_height" format="dimension" />
<attr name="week_bar_view" format="color" />
<attr name="week_line_margin" format="dimension" />

<attr name="week_line_background" format="color" />
<attr name="week_background" format="color" />
<attr name="week_text_color" format="color" />
<attr name="week_text_size" format="dimension" />

<attr name="current_day_text_color" format="color" />
<attr name="current_day_lunar_text_color" format="color" />

       <attr name="calendar_height" format="string" />
<attr name="day_text_size" format="string" />
<attr name="lunar_text_size" format="string" />

<attr name="scheme_text" format="string" />
<attr name="scheme_text_color" format="color" />
<attr name="scheme_month_text_color" format="color" />
<attr name="scheme_lunar_text_color" format="color" />

<attr name="scheme_theme_color" format="color" />

<attr name="selected_theme_color" format="color" />
<attr name="selected_text_color" format="color" />
<attr name="selected_lunar_text_color" format="color" />

<attr name="current_month_text_color" format="color" />
<attr name="other_month_text_color" format="color" />

<attr name="current_month_lunar_text_color" format="color" />
<attr name="other_month_lunar_text_color" format="color" />


<attr name="year_view_month_text_size" format="dimension" />
<attr name="year_view_day_text_size" format="dimension" />
<attr name="year_view_month_text_color" format="color" />
<attr name="year_view_day_text_color" format="color" />
<attr name="year_view_scheme_color" format="color" />

<attr name="min_year" format="integer" />  
 <attr name="max_year" format="integer" />
<attr name="min_year_month" format="integer" />
<attr name="max_year_month" format="integer" />


<attr name="month_view_scrollable" format="boolean" />

<attr name="week_view_scrollable" format="boolean" />

<attr name="year_view_scrollable" format="boolean" />
       

<attr name="month_view_show_mode">
<enum name="mode_all" value="0" /> 
<enum name="mode_only_current" value="1" />
<enum name="mode_fix" value="2" />
attr>


<attr name="week_start_with">
<enum name="sun" value="1" />
<enum name="mon" value="2" />
<enum name="sat" value="7" />
attr>


<attr name="select_mode">
<enum name="default_mode" value="0" />
<enum name="single_mode" value="1" />
<enum name="range_mode" value="2" />
<enum name="multi_mode" value="3" />
attr>


<attr name="max_multi_select_size" format="integer" />


<attr name="min_select_range" format="integer" />
<attr name="max_select_range" format="integer" />


<attr name="month_view_auto_select_day">
<enum name="first_day_of_month" value="0" />
<enum name="last_select_day" value="1" />
<enum name="last_select_day_ignore_current" value="2" />
attr>
declare-styleable>

写在最后,其它各种场景姿势就不多说了,看自己需求去实现。再次注意:Demo只是Demo,只是示例如何使用,与框架本身没有关联,不属于框架一部分


代码下载: CalendarView-master.zip

原文链接:https://github.com/huanghaibin-dev/CalendarView


收起阅读 »

加载反馈页管理框架-LoadSir

LoadSirLoadSir是一个高效易用,低碳环保,扩展性良好的加载反馈页管理框架,在加载网络或其他数据时候,根据需求切换状态页面, 可添加自定义状态页面,如加载中,加载失败,无数据,网络超时,如占位图,登录失效等常用页面。可配合网络加载框架,结合返回 状态...
继续阅读 »

LoadSir

LoadSir是一个高效易用,低碳环保,扩展性良好的加载反馈页管理框架,在加载网络或其他数据时候,根据需求切换状态页面, 可添加自定义状态页面,如加载中,加载失败,无数据,网络超时,如占位图,登录失效等常用页面。可配合网络加载框架,结合返回 状态码,错误码,数据进行状态页自动切换,封装使用效果更佳


使用场景



流程图



LoadSir的功能及特点

  • 支持Activity,Fragment,Fragment(v4),View状态回调
  • 适配多个Fragment切换,及Fragment+ViewPager切换,不会布局叠加或者布局错乱
  • 利用泛型转换输入信号和输出状态,可根据网络返回体的状态码或者数据返回自动适配状态页,实现全局自动状态切换
  • 无需修改布局文件
  • 只加载唯一一个状态视图,不会预加载全部视图
  • 不需要设置枚举或者常量状态值,直接用状态页类类型(xxx.class)作为状态码
  • 可对单个状态页单独设置点击事件,根据返回boolean值覆盖或者结合OnReloadListener使用,如网络错误可跳转设置页
  • 无预设页面,低耦合,开发者随心配置
  • 可保留标题栏(Toolbar,titile view等)
  • 可设置重新加载点击事件(OnReloadListener)
  • 可自定义状态页(继承Callback类)
  • 可在子线程直接切换状态
  • 可设置初始状态页(常用进度页作为初始状态)
  • 可扩展状态页面,在配置中添加自定义状态页
  • 可全局单例配置,也可以单独配置

开始使用LoadSir

LoadSir的使用,只需要简单的三步

添加依赖

compile 'com.kingja.loadsir:loadsir:1.3.8'

第一步:配置

全局配置方式

全局配置方式,使用的是单例模式,即获取的配置都是一样的。可在Application中配置,添加状态页,设置默认状态页

public class App extends Application {
@Override
public void onCreate() {
super.onCreate();
LoadSir.beginBuilder()
.addCallback(new ErrorCallback())//添加各种状态页
.addCallback(new EmptyCallback())
.addCallback(new LoadingCallback())
.addCallback(new TimeoutCallback())
.addCallback(new CustomCallback())
.setDefaultCallback(LoadingCallback.class)//设置默认状态页
.commit()
;
}
}
单独配置方式

如果你即想保留全局配置,又想在某个特殊页面加点不同的配置,可采用该方式。

LoadSir loadSir = new LoadSir.Builder()
.addCallback(new LoadingCallback())
.addCallback(new EmptyCallback())
.addCallback(new ErrorCallback())
.build();
loadService = loadSir.register(this, new Callback.OnReloadListener() {
@Override
public void onReload(View v) {
// 重新加载逻辑
}
});

第二步:注册

在Activity中使用
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_content);
// Your can change the callback on sub thread directly.
LoadService loadService = LoadSir.getDefault().register(this, new Callback.OnReloadListener() {
@Override
public void onReload(View v) {
// 重新加载逻辑
}
});
}}
在View 中使用
ImageView imageView = (ImageView) findViewById(R.id.iv_img);
LoadSir loadSir = new LoadSir.Builder()
.addCallback(new TimeoutCallback())
.setDefaultCallback(LoadingCallback.class)
.build()
;
loadService = loadSir.register(imageView, new Callback.OnReloadListener() {
@Override
public void onReload(View v) {
loadService.showCallback(LoadingCallback.class);
// 重新加载逻辑
}
});
Ps:
[1]要注册RelativeLayoutConstraintLayout的子View,如果该子View被其它子View约束,建议在子View外层再包一层布局,参考
acitivy_view.xm和activity_constraintlayout.xml
在Fragment 中使用

由于Fragment添加到Activitiy方式多样,比较特别,所以在Fragment注册方式不同于上面两种,大家先看模板代码:

@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle
savedInstanceState) {
//第一步:获取布局View
rootView = View.inflate(getActivity(), R.layout.fragment_a_content, null);
//第二步:注册布局View
LoadService loadService = LoadSir.getDefault().register(rootView, new Callback.OnReloadListener() {
@Override
public void onReload(View v) {
// 重新加载逻辑
}
});
//第三步:返回LoadSir生成的LoadLayout
return loadService.getLoadLayout();
}

第三步: 回调

直接回调
protected void loadNet() {
// 进行网络访问...
// 进行回调
loadService.showSuccess();//成功回调
loadService.showCallback(EmptyCallback.class);//其他回调
}
转换器回调 (推荐使用)

如果你不想再每次回调都要手动进行的话,可以选择注册的时候加入转换器,可根据返回的数据,适配对应的状态页。

LoadService loadService = LoadSir.getDefault().register(this, new Callback.OnReloadListener() {
@Override
public void onReload(View v) {
// 重新加载逻辑
}}, new Convertor<HttpResult>() {
@Override
public ClassCallback> map(HttpResult httpResult) {
ClassCallback> resultCode = SuccessCallback.class;
switch (httpResult.getResultCode()) {
case SUCCESS_CODE://成功回调
if (httpResult.getData().size() == 0) {
resultCode = EmptyCallback.class;
}else{
resultCode = SuccessCallback.class;
}
break;
case ERROR_CODE:
resultCode = ErrorCallback.class;
break;
}
return resultCode;
}
});

回调的时候直接传入转换器指定的数据类型。

loadService.showWithConvertor(httpResult);

自定义回调页

LoadSir为了完全解耦,没有预设任何状态页,需要自己实现,开发者自定义自己的回调页面,比如加载中,没数据,错误,超时等常用页面, 设置布局及自定义点击逻辑

public class CustomCallback extends Callback {

//填充布局
@Override
protected int onCreateView() {
return R.layout.layout_custom;
}
//当前Callback的点击事件,如果返回true则覆盖注册时的onReloa(),如果返回false则两者都执行,先执行onReloadEvent()。
@Override
protected boolean onReloadEvent(final Context context, View view) {
Toast.makeText(context.getApplicationContext(), "Hello buddy! :p", Toast.LENGTH_SHORT).show();
(view.findViewById(R.id.iv_gift)).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(context.getApplicationContext(), "It's your gift! :p", Toast.LENGTH_SHORT).show();
}
});
return true;
}

//是否在显示Callback视图的时候显示原始图(SuccessView),返回true显示,false隐藏
@Override
public boolean getSuccessVisible() {
return super.getSuccessVisible();
}

//将Callback添加到当前视图时的回调,View为当前Callback的布局View
@Override
public void onAttach(Context context, View view) {
super.onAttach(context, view);
}

//将Callback从当前视图删除时的回调,View为当前Callback的布局View
@Override
public void onDetach() {
super.onDetach(context, view);
}

}

动态修改Callback

loadService = LoadSir.getDefault().register(...);
loadService.setCallBack(EmptyCallback.class, new Transport() {
@Override
public void order(Context context, View view) {
TextView mTvEmpty = (TextView) view.findViewById(R.id.tv_empty);
mTvEmpty.setText("fine, no data. You must fill it!");
}
});

LoadSir自带便携式Callback

ProgressCallback loadingCallback = new ProgressCallback.Builder()
.setTitle("Loading", R.style.Hint_Title)
.build();

HintCallback hintCallback = new HintCallback.Builder()
.setTitle("Error", R.style.Hint_Title)
.setSubTitle("Sorry, buddy, I will try it again.")
.setHintImg(R.drawable.error)
.build();

LoadSir loadSir = new LoadSir.Builder()
.addCallback(loadingCallback)
.addCallback(hintCallback)
.setDefaultCallback(ProgressCallback.class)
.build();

代码混淆

-dontwarn com.kingja.loadsir.**
-keep class com.kingja.loadsir.** {*;}


代码下载  :LoadSir-master .zip

原文链接  :https://github.com/KingJA/LoadSir

收起阅读 »

Android智能下拉刷新框架-SmartRefreshLayout

Android智能下拉刷新框架-SmartRefreshLayoutSmartRefreshLayout以打造一个强大,稳定,成熟的下拉刷新框架为目标,并集成各种的炫酷、多样、实用、美观的Header和Footer。 正如名字所说,SmartRefreshLa...
继续阅读 »

Android智能下拉刷新框架-SmartRefreshLayout

SmartRefreshLayout以打造一个强大,稳定,成熟的下拉刷新框架为目标,并集成各种的炫酷、多样、实用、美观的Header和Footer。 正如名字所说,SmartRefreshLayout是一个“聪明”或者“智能”的下拉刷新布局,由于它的“智能”,它不只是支持所有的View,还支持多层嵌套的视图结构。 它继承自ViewGroup 而不是FrameLayout或LinearLayout,提高了性能。 也吸取了现在流行的各种刷新布局的优点,还集成了各种炫酷的 Header 和 Footer。

特点功能:

  • 支持多点触摸
  • 支持淘宝二楼和二级刷新
  • 支持嵌套多层的视图结构 Layout (LinearLayout,FrameLayout...)
  • 支持所有的 View(AbsListView、RecyclerView、WebView....View)
  • 支持自定义并且已经集成了很多炫酷的 Header 和 Footer.
  • 支持和 ListView 的无缝同步滚动 和 CoordinatorLayout 的嵌套滚动 .
  • 支持自动刷新、自动上拉加载(自动检测列表惯性滚动到底部,而不用手动上拉).
  • 支持自定义回弹动画的插值器,实现各种炫酷的动画效果.
  • 支持设置主题来适配任何场景的 App,不会出现炫酷但很尴尬的情况.
  • 支持设多种滑动方式:平移、拉伸、背后固定、顶层固定、全屏
  • 支持所有可滚动视图的越界回弹
  • 支持 Header 和 Footer 交换混用
  • 支持 AndroidX
  • 支持横向刷新

智能之处

    智能是什么?有什么用?

智能主要体现 SmartRefreshLayout 对未知布局的自动识别上,这样可以让我们更高效的实现我们所需的功能,也可以实现一些非寻常的功能。 下面通过自定义Header 和 嵌套Layout作为内容 来了解 SmartRefreshLayout 的智能之处。

自定义Header

我们来看这一下这个伪代码例子:

    <SmartRefreshLayout>
<ClassicsHeader/>
<TextView/>
<ClassicsFooter/>
SmartRefreshLayout>

在Android Studio 中的预览效果图

e479f09949e46ba9e92c4c3c39d4925f.jpg

对比代码和我们预想的一样,那我们来对代码做一些改动,ClassicsHeader换成一个简单的TextView,看看会发生什么?

    <SmartRefreshLayout>
<TextView
android:layout_width="match_parent"
android:layout_height="100dp"
android:gravity="center"
android:background="#444"
android:textColor="#fff"
android:text="看看我会不会变成Header"/>
<TextView/>
<ClassicsFooter/>
SmartRefreshLayout>

在Android Studio 中的预览效果图 和 运行效果图

2d3eee1d2fcf024d213fe12f969e56a7.jpgdd72f12c3d967b05187634f49320a359.gif

 

这时发现我们我们替换的 TextView 自动就变成了Header,只是它还不会动。要动起来?那么太简单啦,网上随便一搜索就一大堆的 gif 。

我们选择 环游东京30天:GIF版旅行指南中的这张:

2afef925fdfb303b1c9b0b46e33eca42.gif

接着我们来改代码:

compile 'pl.droidsonroids.gif:android-gif-drawable:1.2.3'//一个开源gif控件
    <SmartRefreshLayout xmlns:app="http://schemas.android.com/apk/res-auto"
app:srlDragRate="0.7"
app:srlHeaderMaxDragRate="1.3">
<pl.droidsonroids.gif.GifImageView
android:layout_width="match_parent"
android:layout_height="150dp"
android:scaleType="centerCrop"
android:src="@mipmap/gif_header_repast"
app:layout_srlSpinnerStyle="Scale"
app:layout_srlBackgroundColor="@android:color/transparent"/>
<ListView/>
<ClassicsFooter/>
SmartRefreshLayout>

在 Android Studio 中的预览效果图 和 运行效果图

 bbd8c7e3d34906f83eaab80fc602019b.jpg8cd5e90aa62ce2b2f449fa4502b42f25.gif


哈哈!一行Java代码都不用写,就完成了一个自定义的Header

嵌套Layout作为内容

如果boss要求在列表的前面固定一个广告条怎么办?这好办呀,一般我们会开开心心的下下这样的代码:

<LinearLayout
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="100dp"
android:gravity="center"
       
android:text=
"我就是boss要求加上的广告条啦"/>
<SmartRefreshLayout>
<ListView/>
SmartRefreshLayout>
LinearLayout>

但是在运行下拉刷新的时候,我们发现 Header是在广告条之下的,看着会别扭~,其实我们可以试试另一种方式,把广告条写到 RefreshLayout内部,看看会发生什么?

<SmartRefreshLayout>
<LinearLayout
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="100dp"
android:gravity="center"
android:text="我就是boos要求加上的广告条啦"/>
<ListView/>
LinearLayout>
SmartRefreshLayout>

由于伪代码过于简单,而且运行效果过于丑陋,这里还是贴出在实际项目中的实际情况吧~

 c0057fa29a0efd57f10ce15e0710c412.gif

我们注意看右边的图,仔细观察手指触摸的位置和下拉效果。可以看到在列表已经滚动到中部时,轻微下拉列表是不会触发刷新的,但是如果是触摸固定的布局,则可以触发下拉。从这里可以看出 SmartRefreshLayout 对滚动边界的判断是动态的,智能的!当然如果 SmartRefreshLayout 的智能还是不能满足你,可以通过 setListener 自己实现滚动边界的判断,更为准确!


代码下载: SmartRefreshLayout-master.zip

原文链接: https://github.com/scwang90/SmartRefreshLayout

收起阅读 »

人脸识别圆形预览效果

FaceCircleView-master.zip原文链接:https://github.com/yangcoder1/FaceCircleView