一个小框架,基于rx_retrofit2_mvp
离职在即,也没什么事情做,就鼓捣了一下。任意搭建了一个小框架,看看以后能不能搞出自己的一个model,好了。不说别的,上代码
1,先上依赖库
compile 'io.reactivex:rxandroid:1.2.1'
compile 'com.squareup.okhttp3:okhttp:3.3.1'
compile 'io.reactivex:rxandroid:1.1.0'
compile 'io.reactivex:rxjava:1.1.0'
compile 'com.squareup.retrofit:retrofit:2.0.0-beta2'
compile 'com.squareup.retrofit:converter-gson:2.0.0-beta2'
compile 'com.squareup.retrofit:adapter-rxjava:2.0.0-beta2'
compile 'com.android.support:design:24.2.1'
compile 'com.android.support:recyclerview-v7:24.2.1'
compile 'com.android.support:cardview-v7:24.2.1'
compile 'com.jakewharton:butterknife:7.0.1'
compile 'com.github.bumptech.glide:glide:3.7.0'
compile 'com.github.chrisbanes.photoview:library:1.2.3'
2。 依赖retrolambda
在app.build依赖
apply plugin: 'me.tatarka.retrolambda'
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
然后在项目的app.build依赖
classpath 'me.tatarka:gradle-retrolambda:3.2.5'
OK,到这里我们的环境搭建就完毕了
3。搭建Http请求模块
- 搭建工具类RetrofitUtils
package mvpmaster.lht.com.lht.utils; import com.squareup.okhttp.OkHttpClient; import java.util.concurrent.TimeUnit; import retrofit.GsonConverterFactory;
import retrofit.Retrofit;
import retrofit.RxJavaCallAdapterFactory; /**
* Created by Ly on 2016/10/14.
*/ public class RetrofitUtils {
private static final int READ_TIMEOUT = 60;//读取超时时间 单位 秒
private static final int CONN_TIMEOUT = 60;//连接超时时间 单位 秒
private static Retrofit retrofit; public RetrofitUtils() {
} public static Retrofit getInstance(String url) {
retrofit = null;
// 初始化一个okhttpClicent的对象 不然ref会自己加入一个
OkHttpClient client = new OkHttpClient();
// 设置读取时间为1分钟
client.setReadTimeout(READ_TIMEOUT, TimeUnit.MINUTES);
// 设置链接时间为12s
client.setConnectTimeout(CONN_TIMEOUT, TimeUnit.SECONDS);
retrofit = new Retrofit.Builder()
.client(client)
.baseUrl(url)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.build();
return retrofit;
}
}Ps,我发现这里并不能做到session的保持,在某些公司的后台并不能支持这种请求库,我稍候会上传一个更新后的版本号
- 这里写上我的一个interface(为什么我用了rx还要有callback???黑人问号脸)
package mvpmaster.lht.com.lht.conf; /**
* Created by Ly on 2016/10/13.
*/ public interface OkHttpCallBack<T> {
void onSuccess(T t);//成功的回调 void onFaild(Throwable e);//失败的回调 void onFinish();
} - 然后是一些比較通用的api文件了,我就不写凝视了
package mvpmaster.lht.com.lht.conf; /**
* Created by Ly on 2016/11/2.
*/ public class HttpConf {
private static final String ZHIHU_BASE_URL = "http://news-at.zhihu.com/api/4/";
private static final String GANK_BASE_URL = "http://gank.io/api/";
private static final String DAILY_BASE_URL = "http://app3.qdaily.com/app3/"; public static String getZhihuBaseUrl() {
return ZHIHU_BASE_URL;
} public static String getGankBaseUrl() {
return GANK_BASE_URL;
} public static String getDailyBaseUrl() {
return DAILY_BASE_URL;
}
}package mvpmaster.lht.com.lht.conf; /**
* Created by Ly on 2016/11/2.
*/ public class HttpStatusConf {
private static final int SUCCESS = 200; public static int getSUCCESS() {
return SUCCESS;
}
}package mvpmaster.lht.com.lht.utils; import mvpmaster.lht.com.lht.ui.beanIml.DailyBean;
import mvpmaster.lht.com.lht.ui.beanIml.NewsDetailBean;
import mvpmaster.lht.com.lht.ui.beanIml.NewsTimeLine;
import retrofit.http.GET;
import retrofit.http.Path;
import rx.Observable; /**
* Created by Ly on 2016/10/14.
*/ public interface APIService { @GET("news/latest")
Observable<NewsTimeLine> getZhiHuList(); @GET("news/before/{time}")
Observable<NewsTimeLine> getBeforetNews(@Path("time") String time); @GET("news/{id}")
Observable<NewsDetailBean> getDetailNews(@Path("id") String id); // for daily
@GET("homes/index/{num}.json")
Observable<DailyBean> getDailyTimeLine(@Path("num") String num); }
- 我们的baseActivity
package mvpmaster.lht.com.lht.base; import android.content.Context;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.design.widget.AppBarLayout;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.util.TypedValue;
import android.view.MenuItem; import butterknife.ButterKnife;
import mvpmaster.lht.com.lht.R; /**
* Created by Ly on 2016/11/2.
*/ public abstract class BaseActivity<V, T extends BasePresenter<V>> extends AppCompatActivity { protected T mPresenter;
private AppBarLayout mAppBar;
private Toolbar mToolbar;
private SwipeRefreshLayout mRefreshLayout; public Context mContext;
private boolean mIsRequestDataRefresh = false; @Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mContext = this;
// 同意为空 不是全部的都要实现这个模式
if (createPresenter() != null) {
mPresenter = createPresenter();
mPresenter.attachView((V) this);
}
setContentView(provideContentViewId());
ButterKnife.bind(this); mAppBar = (AppBarLayout) findViewById(R.id.app_bar_layout);
mToolbar = (Toolbar) findViewById(R.id.toolbar);
if (mToolbar != null && mAppBar != null) {
setSupportActionBar(mToolbar); //把Toolbar当做ActionBar给设置 if (canBack()) {
ActionBar actionBar = getSupportActionBar();
if (null != actionBar) {
//设置ActionBar一个返回箭头。主界面没有,次级界面有
actionBar.setDisplayHomeAsUpEnabled(true);
}
if (Build.VERSION.SDK_INT >= 21) {
//Z轴浮动
mAppBar.setElevation(10.6F);
}
}
}
if (isSetRefresh()) {
setupSwipeRefresh();
} } public static void toIntent(Context context, String... str) {
} @Override
public boolean onOptionsItemSelected(MenuItem item) {
// 此时android.R.id.home即为返回箭头
if (item.getItemId() == android.R.id.home) {
onBackPressed();
finish();
return true;
} else {
return super.onOptionsItemSelected(item);
}
} @Override
protected void onDestroy() {
super.onDestroy();
if (mPresenter != null) {
mPresenter.detachView();
}
} /**
* 生成下拉刷新
*/
private void setupSwipeRefresh() {
mRefreshLayout = (SwipeRefreshLayout) findViewById(R.id.swipe_refresh);
if (null != mRefreshLayout) {
mRefreshLayout.setColorSchemeResources(R.color.colorAccent, R.color.colorPrimary);
mRefreshLayout.setProgressViewOffset(true,
0,
(int) TypedValue.applyDimension
(TypedValue.COMPLEX_UNIT_DIP, 24, getResources()
.getDisplayMetrics()));
}
} /**
* 设置刷新
*
* @param requestDataRefresh
*/
public void setRefresh(boolean requestDataRefresh) {
if (mRefreshLayout == null) {
return;
}
if (!requestDataRefresh) {
mIsRequestDataRefresh = false;
mRefreshLayout.postDelayed(() -> {
if (mRefreshLayout != null) {
mRefreshLayout.setRefreshing(false);
}
}, 1000);
} else {
mRefreshLayout.setRefreshing(true);
}
} /**
* 数据刷新
*/
public void requestDataRefresh() {
mIsRequestDataRefresh = true;
} /**
* 推断当前 Activity 是否同意返回
* 主界面不同意返回,次级界面同意返回
*
* @return false
*/
public boolean canBack() {
return false;
} /**
* 推断子Activity是否须要刷新功能
*
* @return false
*/
public Boolean isSetRefresh() {
return false;
} /**
* 创建P
*
* @return T
*/
protected abstract T createPresenter(); /**
* 用于引入布局文件
*
* @return
*/
abstract protected int provideContentViewId();
}package mvpmaster.lht.com.lht.base; import android.content.Context;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.widget.SwipeRefreshLayout;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup; import butterknife.ButterKnife;
import mvpmaster.lht.com.lht.R; /**
* Created by Ly on 2016/11/2.
*/ public abstract class BaseFragment<V, T extends BasePresenter<V>> extends Fragment {
protected Context mContext;
protected T mPresenter; private boolean mIsRequestDataRefresh = false;
private SwipeRefreshLayout mRefreshLayout; @Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mContext = getActivity();
mPresenter = createPresenter();
mPresenter.attachView((V) this);
} @Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View rootView = inflater.inflate(createViewLayoutId(), container, false);
ButterKnife.bind(this, rootView);
initView(rootView);
if (isSetRefresh()) {
setupSwipeRefresh(rootView);
}
return rootView;
} @Override
public void onDestroy() {
super.onDestroy();
mPresenter.detachView();
} private void setupSwipeRefresh(View view) {
mRefreshLayout = (SwipeRefreshLayout) view.findViewById(R.id.swipe_refresh);
if (mRefreshLayout != null) {
mRefreshLayout.setColorSchemeResources(R.color.refresh_progress_1,
R.color.refresh_progress_2, R.color.refresh_progress_3);
mRefreshLayout.setProgressViewOffset(true, 0, (int) TypedValue
.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 24, getResources().getDisplayMetrics()));
mRefreshLayout.setOnRefreshListener(this::requestDataRefresh);
}
} public void requestDataRefresh() {
mIsRequestDataRefresh = true;
} public void setRefresh(boolean requestDataRefresh) {
if (mRefreshLayout == null) {
return;
}
if (!requestDataRefresh) {
mIsRequestDataRefresh = false;
mRefreshLayout.postDelayed(() -> {
if (mRefreshLayout != null) {
mRefreshLayout.setRefreshing(false);
}
}, 1000);
} else {
mRefreshLayout.setRefreshing(true);
}
} protected abstract T createPresenter(); protected abstract int createViewLayoutId(); protected void initView(View rootView) {
} public Boolean isSetRefresh() {
return true;
}
}还有其它的一些base,我就不一一上传了。
- 说一下我理解的mvp。在我的看法中。mvp,m是要做一些耗时操作的,像读取网络数据,数据库数据。sp数据啊...这些脏活苦活所有都丢给它去做,我们在contract中给它定义好了interface,model类在自己本身去实现它。然后按着上层的要求去做那些苦活累活;而View呢?我认为通常是指我们的activity或者fragment巴,他们就负责一些比較轻松的东西了。像显示个toast啊,show一下dialog啊,拿一下editext的数据啊。最苦力也就是设置个适配器啊。监听一下滑动啊之类的,反正最轻松的那个就是它了;然后就是presenter了,这个类我认为挺难弄的,类似于红娘吧,它也要实现Contract的interface,并且要持有model和view的引用。在interface的回调里面去操控model类去做耗时操作,然后在对应的callback(怎么又是callback?)去操控view去实现各种交互。(不要喷我说得那么模糊,可是这样的东西写不出来,用了就会有这样的想法,并且。用了一次mvp,你就不会再想去用mvc了)
- 我们举一个样例,首页那里拿取了知乎的信息,用了一个recyclerview去显示拿到的数据。我们就用它来讲,我先上传一波项目文件夹,不然太懵逼了。
- 我们先看我们的fragment
package mvpmaster.lht.com.lht.ui.fragment.zhuhu; import android.os.Bundle;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.widget.Toast; import butterknife.Bind;
import mvpmaster.lht.com.lht.R;
import mvpmaster.lht.com.lht.base.BaseFragment;
import mvpmaster.lht.com.lht.ui.adapter.ZhiHuAdapter;
import mvpmaster.lht.com.lht.ui.beanIml.NewsTimeLine; /**
* Created by Ly on 2016/11/2.
*/ public class ZhiHuFragment extends BaseFragment<ZhiHuContract.ZhiHuView, ZhiHuPresenter> implements ZhiHuContract.ZhiHuView {
@Bind(R.id.content_list)
RecyclerView mRlvZhiHu; private LinearLayoutManager mLayoutManager;
private ZhiHuAdapter zhiHuAdapter; // 最后一个可见的视图
private int lastVisibleItem;
// 是否载入过很多其它
private boolean isLoadMore = false;
// 知乎日报须要的下一个參数
private String time; /**
* 初始化配置
*/
private void initConf() {
// 适配器
zhiHuAdapter = new ZhiHuAdapter(getActivity());
// manager
mLayoutManager = new LinearLayoutManager(getActivity());
mRlvZhiHu.setLayoutManager(mLayoutManager);
mRlvZhiHu.setAdapter(zhiHuAdapter);
// 启动自己主动刷新配置
setDataRefresh(true);
// 获取第一次的数据
mPresenter.getDataList();
// 检測recView的滑动状态
scrollRecycleView();
} /**
* recyclerView Scroll listener , maybe in here is wrong ?
*/
public void scrollRecycleView() {
mRlvZhiHu.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
if (newState == RecyclerView.SCROLL_STATE_IDLE) {
lastVisibleItem = mLayoutManager
.findLastVisibleItemPosition();
if (mLayoutManager.getItemCount() == 1) {
zhiHuAdapter.updateLoadStatus(zhiHuAdapter.getLOAD_MORE());
return;
}
if (lastVisibleItem + 1 == mLayoutManager
.getItemCount()) {
zhiHuAdapter.updateLoadStatus(zhiHuAdapter.getLOAD_PULL_TO());
isLoadMore = true;
zhiHuAdapter.updateLoadStatus(zhiHuAdapter.getLOAD_MORE());
mPresenter.getBeforeDateList(time);
}
}
} @Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
lastVisibleItem = mLayoutManager.findLastVisibleItemPosition();
}
});
} @Override
public void requestDataRefresh() {
super.requestDataRefresh();
setDataRefresh(true);
mPresenter.getDataList();
} @Override
protected ZhiHuPresenter createPresenter() {
return new ZhiHuPresenter(this);
} @Override
protected int createViewLayoutId() {
return R.layout.fragment_zhihu;
} @Override
public void setDataRefresh(boolean refresh) {
setRefresh(refresh);
} public static ZhiHuFragment newInstance() {
Bundle args = new Bundle();
ZhiHuFragment fragment = new ZhiHuFragment();
fragment.setArguments(args);
return fragment;
} @Override
public void loadFinishAndReset(NewsTimeLine newsTimeLine, String time) {
zhiHuAdapter.resetData(newsTimeLine);
setDataRefresh(false);
this.time = time;
} @Override
public void loadFinishAndAdd(NewsTimeLine newsTimeLine, String time) {
zhiHuAdapter.addData(newsTimeLine);
this.time = time;
} @Override
public void loadFailure() {
setDataRefresh(false);
zhiHuAdapter.updateLoadStatus(zhiHuAdapter.getLOAD_FAILURE());
} @Override
protected void initView(View rootView) {
super.initView(rootView);
initConf();
} @Override
public void TsShow(String msg) {
Toast.makeText(getActivity(), msg, Toast.LENGTH_SHORT).show();
}
}我们在这个看到一个mPresenter。这个是什么鬼?是从哪里来的?事实上这个就是Presenter的对象了。也是BaseActivity的泛型里面的那个
watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="">我们点取查看它的引用,发如今这个fragment里面一共被引用了3次。第一次是刚进去页面的时候,第一次读取数据。第二个是下拉载入的时候,读取了下一波的数据。再有一次是上拉刷新的时候。我们又一次刷新了一次页面拿取了最新的一波数据,可是?拿数据的在哪里呢?网络请求呢?在哪里??
- 我们看Presenter里面做的操作吧:
package mvpmaster.lht.com.lht.ui.fragment.zhuhu; import mvpmaster.lht.com.lht.base.BasePresenter;
import mvpmaster.lht.com.lht.conf.OkHttpCallBack;
import mvpmaster.lht.com.lht.ui.beanIml.NewsTimeLine; /**
* Created by Ly on 2016/11/2.
*/ public class ZhiHuPresenter extends BasePresenter<ZhiHuContract.ZhiHuView> implements ZhiHuContract.ZhiHuPresenter {
private ZhiHuContract.ZhiHuView zhiHuView;
private ZhiHuContract.ZhiHuModel zhiHuModel; public ZhiHuPresenter(ZhiHuContract.ZhiHuView zhiHuView) {
this.zhiHuView = zhiHuView;
zhiHuModel = new ZhiHuModel();
} @Override
public void getDataList() {
zhiHuModel.getDataList(new OkHttpCallBack<NewsTimeLine>() {
@Override
public void onSuccess(NewsTimeLine newsTimeLine) {
zhiHuView.loadFinishAndReset(newsTimeLine, newsTimeLine.getDate());
} @Override
public void onFaild(Throwable e) {
loadError(e);
zhiHuView.loadFailure();
} @Override
public void onFinish() { }
});
} @Override
public void getBeforeDateList(String time) {
zhiHuModel.getBeforeDateList(time, new OkHttpCallBack<NewsTimeLine>() {
@Override
public void onSuccess(NewsTimeLine newsTimeLine) {
zhiHuView.loadFinishAndAdd(newsTimeLine, newsTimeLine.getDate());
} @Override
public void onFaild(Throwable e) {
loadError(e);
zhiHuView.loadFailure();
} @Override
public void onFinish() { }
});
} private void loadError(Throwable throwable) {
throwable.printStackTrace();
zhiHuView.TsShow(throwable.getMessage());
}
}我们看到有三个ovver的方法。各自是刷新(第一次读取),载入。读取失败 三种情况:我们能够看到,这个类持有了View和Model两个模块,在方法体里面,我们调用了model的方法去做耗时,在结果方法体里面我们调用了view的方法去改动UI,同一时候presenter这个模块又被view持有了,view能够在声明周期里面去调用特定的方法,view和presenter相互沟通。view和model全然隔离,presenter调控model,presenter沟通全局。
一个小框架,基于rx_retrofit2_mvp的更多相关文章
- 利用jdbc简单封装一个小框架(类似DBUtils)
利用jdbc写的一个类似DBUtils的框架 package com.jdbc.orm.dbutils; import java.io.IOException; import java.io.Inpu ...
- mpvue最佳实践 , 美团出的一个小程序框架
看手机微信,看到说美团出了1个小程序框架, mpvue 搜下来试试,看了网上的一个对比 ----------------- 以下为引用 我们对微信小程序.mpvue.WePY 这三个开发框架的主要能 ...
- 通过Dapr实现一个简单的基于.net的微服务电商系统(十四)——开发环境容器调试小技巧
之前有很多同学提到如何做容器调试,特别是k8s环境下的容器调试,今天就讲讲我是如何调试的.大家都知道在vs自带的创建项目模板里勾选docker即可通过F5启动docker容器调试.但是对于启动在k8s ...
- 通过Dapr实现一个简单的基于.net的微服务电商系统(二十)——Saga框架实现思路分享
今天这篇博文的主要目的是分享一下我设计Saga的实现思路来抛砖引玉,其实Saga本身非常的类似于一个简单的工作流体系,相比工作流不一样的部分在于它没有工作流的复杂逻辑处理机制(比如会签),没有条件分支 ...
- 分析一个类似于jquery的小框架
在网上下了一个类似于jQuery的小框架,分析源码,看看怎么写框架. 选择器Select //用沙箱闭包其整个代码,只有itcast和I暴漏在全局作用域 (function( window , und ...
- 一个可以代替冗长switch-case的消息分发小框架
在项目中,我需要维护一个应用层的字节流协议.这个协议的每条报文都是一个字节数组,数组的头两个字节表示消息的传送方向,第三.四个字节表示消息ID,也就是消息种类,再往后是消息内容.时间戳.校验码等……整 ...
- 2、基于wsgiref模块DIY一个web框架
一 web框架 Web框架(Web framework)是一种开发框架,用来支持动态网站.网络应用和网络服务的开发.这大多数的web框架提供了一套开发和部署网站的方式,也为web行为提供了一套通用的方 ...
- spring security 一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架
Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架.它提供了一组可以在Spring应用上下文中 配置的Bean,充分利用了Spring ...
- 任务驱动,对比式学习.NET开发系列之开篇------开源2个小框架(一个Winform框架,一个Web框架)
一 源码位置 1. Winform框架 2. web框架 二 高效学习编程的办法 1 任务驱动方式学习软件开发 大部分人学习软件开发技术是通过看书,看视频,听老师上课的方式.这些方式有一个共同点即按知 ...
随机推荐
- IAR EWARM 字体设置
如果只想简单的设置,可进行如下设置 Tools->IDE Options->Editor->Colors and Fonts->Editor Font->Font 但是这 ...
- SQL Server DATEDIFF() 函数(SQL计算时间差)
select * from task_list where 1=1 and datediff(dd,carateTime,getdate()) =0 定义和用法 DATED ...
- 【优化】COUNT(1)、COUNT(*)、COUNT(常量)、COUNT(主键)、COUNT(ROWID)等
http://blog.itpub.net/26736162/viewspace-2136339/
- MySQL编码latin1转utf8
mysql移植含有中文的数据时,很容易出现乱码问题.很多是在从mysql4.x向mysql5.x移植的时候出现.mysql的缺省字符集是 latin1,在使用mysql4.x的时候,很多人都是用的la ...
- ios7下UISearchBar UITextField 光标不出现的问题
app支持ios7,在UINavBar 里面加入搜索框,结果光标一直出现不了.在overstackflow网站搜索了一下,竟然有人遇到相同的问题.... 解决办法如下: searchBar.tintC ...
- spring mvc改动配置文件路径
1.1. Classpath project文件夹 在web.xml文件例如以下配置: <!-- 配置spring mvc 的核心servlet --> <servlet> ...
- python测试开发django-25.表单提交之post注册案例
前言 一个网站上新用户注册,会写个注册页面,如果用django写个注册页面的流程呢? 本篇以post请求示例,从html页面上输入用户注册信息,提交到后台处理数据,然后传参数据到User数据库表里面 ...
- 天蝎第一季/全集Scorpion迅雷下载
英文译名 Scorpion (第1季) (2014-秋季播出)CBS.本季看点:<天蝎>双名蝎子故事描述一个高深莫测的计算机专家和一群同样具备天才头脑的国际计算机黑客共同组建全球防御网络, ...
- 实用ExtJS教程100例-001:开天辟地的Hello World
ExtJS功能繁多,要想彻底的了解确实很困难.作为初学者,如何能找到一条快速的通道呢?我觉得,如果你有Javascript的基础,那就不要惧怕ExtJS的复杂,从动手开始,遇到问题,解决问题,积累经验 ...
- 好用的批量改名工具——文件批量改名工具V2.0 绿色版
我找了一个绿色免安装的软件来实现批量改名要求 下载地址:http://www.orsoon.com/Soft/14049.html#xiazai 添加图片后,开始改名.通过输入a#就可以将这些图片进行 ...