查看在线pdf文档:
http://note.youdao.com/s/EM20Cggm
以下是我的基本UI组件该系列的文章,欢迎大家转载和分享:
基本UI组件的封装库(一)——basicUI
基本UI组件的封装库(二)——basicUI
基本UI组件的封装库(三)——basicUI
基本UI组件的封装库(四)——basicUI
初衷
- 我们在项目开发的时候,通常会遇到很多不同UI要求,可能每次都要写很多次的布局。
- BasicUI设计初衷就是希望可以提高我们开发的效率和节省时间。
- 如果大家觉得有点帮助,希望可以抬抬你的贵手,送我一个星星,谢谢。如果有什么问题,也欢迎你在下方留言或者在BasicUI中提
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 {
implementation 'com.github.Peakmain:BasicUI:1.1.0-androidx'
}
使用
一、启动器优化
原理
有向无环图的设计
使用
- 1、并发运行,只需要继承Task
public class UtilsTask extends Task {
@Override
public void run() {
//处理数据
}
}
- 2、在某个Task之后执行,比如极光需要在获取设备id之后执行
如:获取设备id的Task
public class DeviceIdTask extends Task {
private String mDeviceId;
@Override
public void run() {
// 真正自己的代码
TelephonyManager tManager = (TelephonyManager) mContext.getSystemService(
Context.TELEPHONY_SERVICE);
mDeviceId = tManager.getDeviceId();
App app = (App) mContext;
app.setDeviceId(mDeviceId);
}
}
极光的Task需要实现dependsOn方法
如下代码
public class JPushTask extends Task {
@Override
public List<Class<? extends Task>> dependsOn() {
List<Class<? extends Task>> tasks = new ArrayList<>();
tasks.add(DeviceIdTask.class);
return tasks;
}
@Override
public void run() {
//模拟极光推送
LogUtils.e("极光推送开始");
App app = (App) mContext;
LogUtils.e("极光推送获取id:",app.getDeviceId());
}
}
- 3、运行在主线程只需要继承MainTask即可
public class WeexTask extends MainTask {
@Override
public void run() {
InitConfig config = new InitConfig.Builder().build();
WXSDKEngine.initialize((Application) mContext, config);
}
}
- 4、想空闲的时候处理数据,只需要在Application中使用DelayInitDispatcher添加相关Task
DelayInitDispatcher delayInitDispatcher=new DelayInitDispatcher();
delayInitDispatcher.addTask(任务即可).start();
- 5、最后只需要在Application中进行初始化和添加运行就可以了
TaskDispatcher.init(this);
TaskDispatcher dispatcher = TaskDispatcher.createInstance();
dispatcher.addTask(new AMapTask())
.addTask(new UtilsTask())
.addTask(new JPushTask())
.addTask(new DeviceIdTask())
.addTask(new WeexTask())
.start();
二、工具类封装
OkHttp的使用
- get请求方式
HttpUtils.with(OkHttpActivity.this)
.url("http://i.jandan.net/")
.addParams("oxwlxojflwblxbsapi", "jandan.get_pic_comments")
.addParams("page", "1")
.execture(new EngineCallBack() {
@Override
public void onError(Exception e) {
LogUtils.e(e.getMessage());
}
@Override
public void onSuccess(String result) {
mTvResult.setText(result);
}
});
- post请求方式
HttpUtils.with(OkHttpActivity.this)
.url("https://www.wanandroid.com/user/login")
.addParams("username", "peakmain")
.addParams("password", "123456")
.post()
.execture(new EngineCallBack() {
@Override
public void onError(Exception e) {
LogUtils.e(e.getMessage());
}
@Override
public void onSuccess(String result) {
mTvResult.setText(result);
}
});
- 单线程下载
File file = new File(Environment.getExternalStorageDirectory(), "test.apk");
if (file.exists()) {
file.delete();
}
HttpUtils.with(OkHttpActivity.this)
.url("http://imtt.dd.qq.com/16891/apk/87B3504EE9CE9DC51E9F295976F29724.apk")
.downloadSingle()
.file(file)
.exectureDownload(new DownloadCallback() {
@Override
public void onFailure(Exception e) {
LogUtils.e(e.getMessage());
}
@Override
public void onSucceed(File file) {
ToastUtils.showShort("file下载完成");
LogUtils.e("文件保存的位置:" + file.getAbsolutePath());
mProgressBar.setVisibility(View.GONE);
mProgressBar.setProgress(0);
}
@Override
public void onProgress(int progress) {
LogUtils.e("单线程下载apk的进度:" + progress);
mProgressBar.setProgress(progress);
mProgressBar.setVisibility(View.VISIBLE);
}
});
- 多线程下载
file = new File(Environment.getExternalStorageDirectory(), "test.apk");
if (file.exists()) {
file.delete();
}
HttpUtils.with(OkHttpActivity.this)
.url("http://imtt.dd.qq.com/16891/apk/87B3504EE9CE9DC51E9F295976F29724.apk")
.downloadMutil()
.file(file)
.exectureDownload(new DownloadCallback() {
@Override
public void onFailure(Exception e) {
LogUtils.e(e.getMessage());
}
@Override
public void onSucceed(File file) {
LogUtils.e(file.getAbsolutePath() + "," + file.getName());
Toast.makeText(OkHttpActivity.this, "下载完成", Toast.LENGTH_LONG).show();
mProgressBar.setVisibility(View.GONE);
mProgressBar.setProgress(0);
}
@Override
public void onProgress(int progress) {
LogUtils.e(progress + "%");
mProgressBar.setVisibility(View.VISIBLE);
mProgressBar.setProgress(progress);
}
});
- 切换引擎的方法
默认是okHttpEngine
HttpUtils.with(OkHttpActivity.this)
.exchangeEngine(切换的引擎)
或者在application中直接初始化
HttpUtils.init(初始化的引擎);
2、 gilde图片选择库切换
- 简单使用
ImageLoader.getInstance().displayImage(this, data.get(0).getUrl(), mImageView);
- 占位图的使用
ImageLoader.getInstance().displayImage(this, data.get(1).getUrl(), mImageView, R.mipmap.ic_default_portrait);
- 圆角图片
ImageLoader.getInstance().displayImageRound(this, data.get(2).getUrl(), mImageView,50 ,0);
- 指定图片的大小
ImageLoader.getInstance().displayImage(this,data.get(4).getUrl(),mImageView,800,800,0);
- 切换图片加载库
ImageLoader.getInstance().exchangeImageLoader(切换的库)
3、Sqlitedatabase数据库封装的使用
- 1、需要实体类实现空的构造方法
-2、获得IDaoSupport实体类
IDaoSupport<Person> dao = DaoSupportFactory.getInstance().getDao(Person.class);
- 3、插入数据
dao.insert(persons);
- 4、查询数据
查询所有数据
List<Person> list = dao.querySupport().queryAll();
查询指定数据:如指定指定名字的范围
dao.querySupport().selection("name like ?").selectionArgs(new String[]{"新%"}).query();
- 5、删除数据
需要传入两个参数
1、条件 2、条件参数
源码
int delete(String whereClause, String... whereArgs);
如下删除年龄109的数据,返回的是删除数据所在位置的索引
dao.delete("age = ?", new String[]{"109"});
- 6、更新数据
需要传入三个参数,1、新数据的实体类,2、条件 3、条件参数
源码
int update(T obj, String whereClause, String... whereArgs);
如下更新年龄是108的数据
dao.update(new Person("peakmain", 18), "age =?", new String[]{"108"});
四、关于Recyclerview的使用
- 单布局继承于 CommonRecyclerAdapter<T>,其中需要的方法是
public CommonRecyclerAdapter(Context context, List<T> data, int layoutId) {}
1、关于设置文本,第一种我们可以直接holder.setText方法
holder.setText(int viewId,CharSequence text)
第二种就是首先getView然后设置文字
TextView tv=holder.getView(view viewId)
tv.setText("")
2、除了设置文本之外,里面还提供了设置文字的颜色,文字的大小,view的点击事件和长按事件,view是否可见,某一条的点击事件
3、关于设置图片,我提供了一个默认Glide加载图片
holder.setImageByUrl(R.id.iv_meizhi, new GlideImageLoader(item.getUrl()));
如果大家不想使用Glide或者想用自己的Glide,大家可以新建一个类去继承ViewHolder.HolderImageLoade即可
for example:
public class PagePoupAdapter extends CommonRecyclerAdapter<String>{
private int mSelectPosition=0;
public PagePoupAdapter(Context context, List<String> data) {
super(context, data, R.layout.item_popup_window);
}
@Override
public void convert(ViewHolder holder, String item) {
TextView tvName=holder.getView(R.id.tv_name);
tvName.setText(item);
tvName.setTextColor(mContext.getResources().getColor(R.color.colorAccent));
}
}
- 关于多布局
也是继承extends CommonRecyclerAdapter<T>.但是实现的方法是
public CommonRecyclerAdapter(Context context, List<T> data, MultiTypeSupport<T> multiTypeSupport) {}
for example:
public SearchCityAdapter(Context context, List<SearchCityBean> data) {
super(context, data, new MultiTypeSupport<SearchCityBean>() {
@Override
public int getLayoutId(SearchCityBean item, int position) {
return R.layout.default_city_recycler_item;
}
});
}
@Override
public void convert(ViewHolder holder, SearchCityBean item) {
int itemViewType = getItemViewType(holder.getAdapterPosition());
if (itemViewType == R.layout.default_city_recycler_item) {
holder.setText(R.id.tv_city_name, item.getName());
}
}
- 我们有时候会使用到recycleview的悬浮列表,这里我也提供了一个基本的悬浮列表BaseSuspenisonItemDecoration,使用很简单,大家只需要继承BaseSuspenisonItemDecoration即可
因为有时候我们需要自定背景颜色和文字颜色,文字的大小,悬浮之间的距离等。这里我提供了Builder方法,大家继承即可
for example:
public class SectionItemDecoration extends BaseItemDecoration<SearchCityBean> {
public SectionItemDecoration(Builder builder) {
super(builder);
}
@Override
public String getTopText(List<SearchCityBean> data, int position) {
return data.get(position).getSection();
}
public static class Builder extends BaseItemDecoration.Builder<SectionItemDecoration.Builder,SearchCityBean>{
public Builder(Context context, List<SearchCityBean> data) {
super(context, data);
}
@Override
public SectionItemDecoration create() {
return new SectionItemDecoration(this);
}
}
}
使用非常简单
SectionItemDecoration decoration = new SectionItemDecoration.Builder(this, mAllCities)
.setBgColor(ContextCompat.getColor(this,R.color.colorAccent))
.setTextColor(ContextCompat.getColor(this,R.color.color_black))
.setSectionHeight(SizeUtils.dp2px(this,30))
.create();
recyclerview实现下拉刷新、加载更多和多状态布局
- 封装库类中我封装了一个默认的下拉刷新和加载更多
- 1、布局
<com.peakmain.ui.recyclerview.view.LoadRefreshRecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
- 2、下拉刷新
mRecyclerView.addRefreshViewCreator(new DefaultRefreshViewCreator());
mRecyclerView.setOnRefreshListener(this);
- 3、重写onRefresh的方法,数据获取完之后在其中调用onStopRefresh方法停止下拉刷新
比如示例中的代码
public void onRefresh() {
new Handler().postDelayed(() -> {
List<String> data = getData();
mAdapter.setData(data);
mRecyclerView.onStopRefresh();
}, 2000);
}
- 4、加载更多
mRecyclerView.addLoadViewCreator(new DefalutLoadViewCreator());
mRecyclerView.setOnLoadMoreListener(this);
- 5、重写onLoad方法,数据获取完之后在其中调用onStopLoad方法停止加载更多
比如示例中的代码
public void onLoad() {
new Handler().postDelayed(() -> {
List<String> moreData = getMoreData();
mAdapter.addData(moreData);
mRecyclerView.onStopLoad();
}, 2000);
}
- 6、大家还可以自定以下拉刷新和加载更多
下拉刷新继承RefreshViewCreator实现相关功能即可
上图下拉刷新效果2示例代码
public class BestMissRefreshCreator extends RefreshViewCreator {
// 加载数据的ImageView
private ImageView mRefreshIv;
@Override
public View getRefreshView(Context context, ViewGroup parent) {
View refreshView = LayoutInflater.from(context).inflate(R.layout.layout__bestmiss_refresh_header_view, parent, false);
mRefreshIv = refreshView.findViewById(R.id.img_progress);
return refreshView;
}
@Override
public void onPull(int currentDragHeight, int refreshViewHeight, int currentRefreshStatus) {
if (currentRefreshStatus == LoadRefreshRecyclerView.LOAD_STATUS_PULL_DOWN_REFRESH) {
mRefreshIv.setImageResource(R.drawable.list_view_pull);
}
if (currentRefreshStatus == LoadRefreshRecyclerView.LOAD_STATUS_LOOSEN_LOADING) {
mRefreshIv.setImageResource(R.drawable.list_view_release);
}
}
@Override
public void onRefreshing() {
mRefreshIv.setImageResource(R.drawable.load_more_anim);
((AnimationDrawable) mRefreshIv.getBackground()).start();
}
@Override
public void onStopRefresh() {
// 停止加载的时候清除动画
mRefreshIv.setRotation(0);
((AnimationDrawable) mRefreshIv.getBackground()).stop();
mRefreshIv.clearAnimation();
}
}
加载更多继承LoadViewCreator即可
上图下拉刷新2示例代码
public class LoadMoreCreator extends LoadViewCreator {
// 加载数据的ImageView
private TextView mLoadTv;
private View mRefreshIv;
@Override
public View getLoadView(Context context, ViewGroup parent) {
View refreshView = LayoutInflater.from(context).inflate(R.layout.layout_load_footer_view, parent, false);
mLoadTv = (TextView) refreshView.findViewById(R.id.load_tv);
mRefreshIv = refreshView.findViewById(R.id.refresh_iv);
return refreshView;
}
@Override
public void onPull(int currentDragHeight, int refreshViewHeight, int currentRefreshStatus) {
if (currentRefreshStatus == LoadRefreshRecyclerView.LOAD_STATUS_PULL_DOWN_REFRESH) {
mLoadTv.setText("上拉加载更多");
}
if (currentRefreshStatus == LoadRefreshRecyclerView.LOAD_STATUS_LOOSEN_LOADING) {
mLoadTv.setText("松开加载更多");
}
}
@Override
public void onLoading() {
mLoadTv.setVisibility(View.INVISIBLE);
mRefreshIv.setVisibility(View.VISIBLE);
// 加载的时候不断旋转
RotateAnimation animation = new RotateAnimation(0, 720,
Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
animation.setRepeatCount(-1);
animation.setDuration(1000);
mRefreshIv.startAnimation(animation);
}
@Override
public void onStopLoad() {
// 停止加载的时候清除动画
mRefreshIv.setRotation(0);
mRefreshIv.clearAnimation();
mLoadTv.setText("上拉加载更多");
mLoadTv.setVisibility(View.VISIBLE);
mRefreshIv.setVisibility(View.INVISIBLE);
}
@Override
public void onFinishLoadData() {
mLoadTv.setText("无更多数据");
}
}
RecycleView实现多状态布局
使用
使用WarpRecyclerView(只能添加头部或尾部)、RefreshRecyclerView(下拉刷新)或者LoadRefreshRecyclerView(加载更多或者下拉刷新)
- 1、可以自定义状态布局,一共四个属性
<declare-styleable name="MultipleStatusView">
<attr name="loadingView" format="reference" />
<attr name="errorView" format="reference" />
<attr name="emptyView" format="reference" />
<attr name="noNetworkView" format="reference" />
</declare-styleable>
所以使用示例如下
<com.peakmain.ui.recyclerview.view.LoadRefreshRecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:emptyView="@layout/layout_empty_view"
app:errorView="@layout/layout_error_view"
app:noNetworkView="@layout/layout_network_view"
app:loadingView="@layout/layout_loading_view"/>
注意:这里的emptyView、errorView、noNetworkView布局的根布局需要设置id,对应的id分别是:empty_retry_view、error_retry_view、no_network_retry_view
2、basicUI提供了默认的状态布局,可不设置自定义属性
3、没有网络显示
mRecyclerView.showNoNetwork();
- 4、空布局
这里大家放在什么位置都可以,源码中会自动根据是否有数据进行显示或隐藏
mRecyclerView.showEmptyView();
- 5、显示loading和隐藏loading
mRecyclerView.showLoading();
mRecyclerView.hideLoading();
- 6、显示错误
mRecyclerView.showError();
- 7、显示内容
mRecyclerView.showContentView();
- 8、设置emptyView、errorView、noNetworkView可以设置点击事件
mRecyclerView.setOnRetryClickListener(v -> ToastUtils.showShort("正在重新请求接口..."));
五、关于NavigationBar的使用
我们每次在项目添加头部的时候,一般做法都是说定义一个公用的布局,但是这其实并不友好,而且都需要findVIewById,我这里用了Builder设计模式,可以动态添加头部
mDefaultNavigationBar = new DefaultNavigationBar
.Builder(this, (ViewGroup) findViewById(android.R.id.content))
//Whether to display the return button
.setDisplayHomeAsUpEnabled(true)
//Set left click event
.setLeftClickListener(v -> {
})
//Whether to display the title that comes with the toolbar by default
.setDisplayShowTitleEnabled(true)
//Hide right view
.hideRightView()
//Set the click event of the return button
.setNavigationOnClickListener(v -> finish())
//set left text color
.setLeftTextColor(ContextCompat.getColor(this,R.color.color_272A3D))
//set title
.setTitleText("")
//set toolbar background color
.setToolbarBackgroundColor(0)
.create();
六、关于AlertDialog
支持从底部弹出,支持宽度全屏,支持设置动画
AlertDialog dialog = new AlertDialog.Builder(ImagePreviewActivity.this)
.setContentView(R.layout.dialog_show_image_deal)
.fromButtom(true)
// Set click events for view
.setOnClickListener(R.id.bt_logout, new View.OnClickListener() {
@Override
public void onClick(View v) {
}
})
//set animation
.setAnimation(R.style.dialog_from_bottom_anim)
//Eject from bottom
.fromButtom(true)
//set width MATCH_PARENT
.setFullWidth()
.show();
七、关于PopupWindow的封装
new CustomPopupWindow.PopupWindowBuilder(this)
.setView(R.layout.popup_window_view)
.enableBackgroundDark(true)
.setAnimationStyle(R.style.PopupWindowAnimation)
.setBgDarkAlpha(0.7f)
.create();
八、TextView的封装
这个支持设置背景颜色,背景的圆角,线条的颜色,线条的宽度,支持文字上下左右图片资源两者居中,减少布局嵌套,使用方法还是android:drawableLeft=""
<com.peakmain.ui.widget.ShapeTextView
android:id="@+id/shape_text_view"
android:layout_width="@dimen/space_100"
android:layout_height="@dimen/space_100"
android:layout_centerInParent="true"
android:gravity="center"
android:textColor="@color/color_white"
android:textSize="28sp"
tools:text="A"
app:shapeTvRadius="@dimen/space_6"
app:shapeTvBackgroundColor="#333333" />
说明:一共有四个属性:shapeTvStrokeWidth线的宽度、shapeTvStrokeColor线的颜色、shapeTvRadius圆角的半径、shapeTvBackgroundColor背景颜色
九、EditText的封装
自带清除按钮的图标,并且可对删除按钮进行填色,或者对删除按钮图标进行替换
<com.peakmain.ui.widget.AutoDeleteEditText
android:id="@+id/edt_address"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="3"
android:paddingBottom="@dimen/spacing_5"
app:ad_HintColor="@color/color_9B9B9B"
app:ad_TextColor="@color/color_4A4A4A"
app:ad_TextSize="@dimen/font_16"
app:ad_hint="@string/input_details_address"
app:ad_paddingTop="@dimen/spacing_15"
app:ad_top="true" />
说明:一共有11个属性:字体大小adet_text_size、字体颜色adet_text_color、未输入文字时字体颜色adet_hint_color、内容是否居上adet_isTop、内容内边距离顶部的距离adet_padding_top、hint的资源adet_hint、是否单行adet_isSingle、输入类型android:inputType、输入长度adet_max_length、删除的图片的颜色adet_tint_color、删除图片的资源adet_delete_src
结语
如果大家感兴趣想知道更多的使用,大家可以看我实战项目wanandorid
我的项目BasicUI的Github地址:https://github.com/Peakmain/BasicUI