實現PHP伺服器+Android客戶端(Retrofit+RxJava)第二天客戶端介面的大致實現
本篇文章講的是客戶端的部分,也會丟擲一些伺服器端實現的想法。
上一篇文章: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
具體還有其他的生命週期的變化情況大家可以看這篇文章:
另外還要注意的一點是包的匯入,有v4的包還,有一個app的包,錯誤的匯入會出現一些比如新增到返回棧沒有效果等問題,app.Fragment的包對3.0以下的版本不相容,v4的包可以相容到1.6的版本,要注意的就是使用v4的包是,如果我們的xml中使用了fragment標籤那麼activity就要繼承FragmentActivity,就我目前程式碼寫下來就這裡會有問題,其他的問題還沒有遇到,如果有其他問題可以參考
如果fragment寫的比較複雜的話就需要去看看這篇文章:Fragment全解析(1):那些年踩過的坑
不過我這裡邏輯還算簡單,也沒遇到什麼大坑,就在輪播圖哪裡會有些問題,這個稍後會提到。先來進入正題,講一下這篇文章要講的內容:就是客戶端介面的大致實現,大體效果類似於掌上英雄聯盟(當然我這裡是簡化了一些的效果)
首先可以看到的是固定不動的底部和上面的viewpager,這個使用過掌上英雄聯盟應該一看就能看出來。這裡就用到了fragment的知識,他的介面架構大致上就是一個activity多個fragment這樣。知道了這個就來跟著我寫吧!
首先一個MainActivity,在其對應的xml實現如下:
<LinearLayout xmlns:android="http://schemas.android.com/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.com/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。
程式碼:這裡我還沒來得及做螢幕適配的工作,我用的平板做的。