本篇文章讲的是客户端的部分,也会抛出一些服务器端实现的想法。
上一篇文章:PHP服务器+Android客户端(Retrofit+RxJava)实践第一天 连通了PHP服务器和Android客户端,客户端请求,服务器响应之后在客户端打印了hello world,Android客户端的网络请求部分使用的是Retrofit+RxJava,其实还有Okhttp(因为名字太长了就没加在标题里)。既然已经能搭一个简单的服务器了,那么自然就想把这个项目做的大一些。正好这两天也没什么事要做,就一直在想改做一个怎么样的应用,考虑到要多学一些技术,所以就想到做一个资讯浏览之类的东西,能浏览图片、看视频等等的。
界面模块的搭建
这个标题我也不知道要怎么取,我想表达的是在我们一开始学习android的时候肯定都是全部使用Activity,但是我看了一些文章都在说要多使用fragment,而且前段时间去面试也被问到了,然后因为没怎么用过,连fragment的生命周期都没回答出来。这里我自己也为了学习fragment,所以界面这块我采用的使用单个activity多了fragment,fragment中再嵌套fragment的方式。先来看看fragment的生命周期
再来看看和activity的生命周期的对比
创建就不说了,使用会比较多的也就切换到其他fragment(在addtoBackStack的前提下):
onPause
onStop
onDestroyView
切回来的时候是(在addtoBackStack的前提下):
onCreateView
onActivityCreated
onStart
onResume
上面都是在添加到了返回栈的情况下,如果没有添加到返回栈的话:
onPause
onStop
onDestroyView
onDestroy
onDetach
具体还有其他的生命周期的变化情况大家可以看这篇文章:理解Fragment生命周期
另外还要注意的一点是包的导入,有v4的包还,有一个app的包,错误的导入会出现一些比如添加到返回栈没有效果等问题,app.Fragment的包对3.0以下的版本不兼容,v4的包可以兼容到1.6的版本,要注意的就是使用v4的包是,如果我们的xml中使用了fragment标签那么activity就要继承FragmentActivity,就我目前代码写下来就这里会有问题,其他的问题还没有遇到,如果有其他问题可以参考
http://blog.csdn/a465456465/article/details/10415211
http://www.tuicool/articles/NJRjuq
http://blog.csdn/onlysnail/article/details/45726235
如果fragment写的比较复杂的话就需要去看看这篇文章:Fragment全解析(1):那些年踩过的坑
不过我这里逻辑还算简单,也没遇到什么大坑,就在轮播图哪里会有些问题,这个稍后会提到。先来进入正题,讲一下这篇文章要讲的内容:就是客户端界面的大致实现,大体效果类似于掌上英雄联盟(当然我这里是简化了一些的效果)
首先可以看到的是固定不动的底部和上面的viewpager,这个使用过掌上英雄联盟应该一看就能看出来。这里就用到了fragment的知识,他的界面架构大致上就是一个activity多个fragment这样。知道了这个就来跟着我写吧!
首先一个MainActivity,在其对应的xml实现如下:
<LinearLayout xmlns:android="http://schemas.android/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:id="@+id/id_context"
android:layout_width="match_parent"
android:layout_weight="1"
android:layout_height="0dp"/>
<include layout="@layout/bottomlayout"/>
</LinearLayout>
这里我把底部单独写在了一个xml中,这是好的习惯,不管是能不能复用,像这种底部啊,标题栏什么的还是都写在单独的xml中比较好,然后使用include标签包进去(还有merge标签和viewstub标签都是界面优化的利器,这里不做介绍可参考:性能优化之布局优化)
xml就是这样,activity中的代码的话也就是监听一下底部的几个按钮按下(我这里就只有两个),然后切换一下fragment(准确说是替换,使用的是getSupportFragmentManager() .beginTransaction().replace).这里我切换的就是RightFragment和LeftFragment。
LeftFragment的xml如下
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v4.view.ViewPager
android:id="@+id/left_viewpager"
android:layout_width="match_parent"
android:layout_height="match_parent" >
</android.support.v4.view.ViewPager>
<include layout="@layout/lfragmenttitle"/>
</FrameLayout>
在LeftFragment的代码中实现的就是滑动切换fragment的效果(我这里切换的是如下的几个fragment:HeadlineFragment、TextFragment、PicFragment、VideoFragment),这几个fragment界面上相关代码也还算简单我主要花了点时间的也就是HeadlineFragment的轮播图,找了一些好像都差不多于是自己写了一个(仿照的ios版掌上英雄联盟的那个轮播图效果,但是那个滑动到最后一项后继续向后滑动能滑到第一页的效果没有实现),不废话直接上代码:
public class SlideView extends FrameLayout implements ViewPager.OnPageChangeListener{
//播放延迟
private int delay = 1000;
private long recentSlideTime;
private boolean showingState;
Context con;
ViewPager vp;
LinearLayout ll;
private int mCurrentItem;
Timer timer;
public SlideView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
public SlideView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public SlideView(Context context) {
super(context);
init(context);
}
private void init(Context context){
con = context;
ViewGroup vg = (ViewGroup) LayoutInflater.from(context).inflate(R.layout.slideview, this, true);
vp = (ViewPager) vg.findViewById(R.id.slide_viewpager);
vp.addOnPageChangeListener(this);
ll = (LinearLayout) vg.findViewById(R.id.slide_circles);
showingState = true;
}
public SlideView setAdapter(PagerAdapter adapter){
vp.setAdapter(adapter);
initcCircle(adapter);
return this;
}
public SlideView setAdapter(FragmentStatePagerAdapter adapter){
vp.setAdapter(adapter);
initcCircle(adapter);
return this;
}
public PagerAdapter getAdapter(){
return vp.getAdapter();
}
public SlideView setDelay(int timeMs){
delay = timeMs;
return this;
}
public void startPlay(){
if(timer!=null){
timer.cancel();
}
timer = new Timer();
timer.schedule(new WeakTimerTask(this), delay, delay);
}
private void stopPlay(){
if (timer!=null){
timer.cancel();
timer = null;
}
}
private void initcCircle(PagerAdapter pa){
int pad = TranslateUtils.dp2px(2,con);
for (int i=0;i<pa.getCount();i++){
ImageView v = new ImageView(con);
v.setImageResource(R.drawable.circle_u);
v.setPadding(pad,pad,pad,pad);
ll.addView(v);
}
}
public SlideView setCurrentItem(int position){
vp.setCurrentItem(position);
setCircleRes(position);
return this;
}
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
@Override
public void onPageSelected(int position) {
mCurrentItem = position;
recentSlideTime = System.currentTimeMillis();
setCircleRes(position);
}
@Override
public void onPageScrollStateChanged(int state) {
}
public int getCurrentItem(){
return mCurrentItem;
}
private void setCircleRes(int pos){
for(int i=0;i<ll.getChildCount();i++){
ImageView img = (ImageView) ll.getChildAt(i);
img.setImageResource(R.drawable.circle_u);
}
ImageView img = (ImageView) ll.getChildAt(pos);
img.setImageResource(R.drawable.circle_p);
}
public void showingState(boolean state){
showingState = state;
}
public boolean getShowingState(){
return showingState;
}
private static class WeakTimerTask extends TimerTask {
private WeakReference<SlideView> mSlideViewWeakReference;
public WeakTimerTask(SlideView mRollPagerView) {
this.mSlideViewWeakReference = new WeakReference<>(mRollPagerView);
}
@Override
public void run() {
SlideView slideView = mSlideViewWeakReference.get();
if (slideView!=null){
if(slideView.showingState&&System.currentTimeMillis()-slideView.recentSlideTime>slideView.delay){
slideView.mHandler.sendEmptyMessage(0);
}
}else{
cancel();
}
}
}
private final static class TimeTaskHandler extends Handler {
private WeakReference<SlideView> mSlideViewWeakReference;
public TimeTaskHandler(SlideView rollPagerView) {
this.mSlideViewWeakReference = new WeakReference<>(rollPagerView);
}
@Override
public void handleMessage(Message msg) {
SlideView slideView = mSlideViewWeakReference.get();
if(slideView==null){
return;
}
int cur = slideView.vp.getCurrentItem()+1;
if(cur>=slideView.vp.getAdapter().getCount()){
cur=0;
}
slideView.vp.setCurrentItem(cur);
if (slideView.vp.getAdapter().getCount()<=1) {
slideView.stopPlay();
}
}
}
private TimeTaskHandler mHandler = new TimeTaskHandler(this);
}
我用的是组合控件的方式实现的,其实很简单,使用的话也算方便,后续还会优化(很多属性我还没有自定义,太懒了有些),最近有没那么懒的话我会放到github上,希望到时候各位看官也commit。
好了上面我说我也在使用fragment的时候遇到了一个问题,就是在轮播图这里,我的轮播图的实现也是基于viewpager的,如果其中的adapter使用的是FragmentStatePagerAdapter的话就会遇到从HeadlineFragment滑到PicFragment只有在滑回到HeadlineFragment的时候轮播图在一段时间内是空白的没有内容,不要急着去跟踪代码,先回顾下结构。
首先一个activity,里面装了一层fragment,是RightFragment和LeftFragment之间切换,然后在LeftFragment中又有一层fragment(用viewpager来滑动切换),里面是HeadlineFragment、TextFragment、PicFragment、VideoFragment这四个之间的切换,然后轮播图在HeadlineFragment,又是一层fragment,也就是说这里有三层fragment的嵌套:第一层:LeftFragment、RightFragment,第二层:HeadlineFragment、TextFragment、PicFragment、VideoFragment,第三层:轮播图。
结构清晰了问题回来,我们看到第二层fragment,使用viewpager实现的,而viewpager的adapter使用的是FragmentStatePagerAdapter的话默认缓存一页,也就是说HeadlineFragment(在第一页)滑动到PicFragment(在第三页)的时候HeadlineFragment是已经被销毁了,按理说不会有什么问题,怎么回事呢,只能跟踪代码,发现是没有执行FragmentStatePagerAdapter的getItem方法,导致要传进去的参数没有传进去(图片的id),而导致没有执行getItem方法的原因好像是HeadlineFragment的fragment栈没有被清理掉,很奇怪啊!问题在于viewpager切换的时候被销毁的fragment没有清理干净。暂时先不管这个,先解决问题再说,找了一些方法最后使用在onSaveInstanceState中保存参数解决了。
好了,这一篇文章就到这里,其实好像也没什么内容。
下一篇实现的是客户端网络请求部分,使用的是Retrofit+RxJava。
代码:这里我还没来得及做屏幕适配的工作,我用的平板做的。
更多推荐
实现PHP服务器+Android客户端(Retrofit+RxJava)第二天客户端界面的大致实现
发布评论