ListView增加下拉刷新,上拉加載更多

項(xiàng)目簡(jiǎn)介

為L(zhǎng)istView增加header和footer來實(shí)現(xiàn)下拉刷新和上拉加載更多,已經(jīng)是Android開發(fā)老生常談的問題,網(wǎng)絡(luò)上有數(shù)不清的demo來方便你的項(xiàng)目開發(fā),但是網(wǎng)絡(luò)上這么多的資源導(dǎo)致你在選擇第三方ListView時(shí)往往會(huì)陷入選擇困難,與其每次都要糾結(jié)哪一家的第三方ListView比較完美,倒不如參照這些第三方ListView打造出自己想要的ListView。

項(xiàng)目結(jié)構(gòu)

項(xiàng)目結(jié)構(gòu)比較清晰,主要分為三部分:header,footer,以及自定義的ListView,在這個(gè)項(xiàng)目中,筆者將其定義為:RefreshHeader,RefreshFooter以及RefreshListView

RefreshHeader實(shí)現(xiàn)

首先來看RefreshHeader的截圖:

0.png
1.png
2.png

從圖中我們可以清晰的看出圖中有三種狀態(tài):

  • 下拉刷新
  • 松開刷新
  • 正在加載

創(chuàng)建RefreshHeader.java文件讓其extendsLinearLayout,可以為其增加三種狀態(tài)并初始化狀態(tài):

public static final int PULL_TO_REFRESH = 0;    //下拉刷新
public static final int RELEASE_TO_REFRESH = 1; //松開刷新
public static final int REFRESHING = 2;         //正在刷新
int mState = PULL_TO_REFRESH;

編寫RefreshHeader我們可以分為四步來執(zhí)行:

  1. 編寫布局文件
  2. 根據(jù)狀態(tài)改變RefreshHeader界面顯示
  3. 根據(jù)手指拖動(dòng)位置來改變RefreshHeader高度和狀態(tài)
  4. 根據(jù)手指松開位置再次改變狀態(tài),滾動(dòng)至相應(yīng)位置

首先編寫布局文件:

refresh_header.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:gravity="bottom"
    android:orientation="vertical">

    <RelativeLayout
        android:id="@+id/header_content"
        android:layout_width="match_parent"
        android:layout_height="@dimen/refresh_header_height">


        <LinearLayout
            android:id="@+id/ll_pull_state"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerInParent="true"
            android:gravity="center"
            android:orientation="vertical">

            <TextView
                android:id="@+id/tv_pull_state"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="下拉刷新" />
        </LinearLayout>

        <ProgressBar
            android:id="@+id/progressBar"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerVertical="true"
            android:layout_gravity="center"
            android:layout_marginRight="10dp"
            android:layout_toLeftOf="@id/ll_pull_state"
            android:visibility="invisible" />

        <ImageView
            android:id="@+id/iv_arrow"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerVertical="true"
            android:layout_marginRight="15dp"
            android:layout_toLeftOf="@id/ll_pull_state"
            android:src="@drawable/arrow_down" />
    </RelativeLayout>

</LinearLayout>

布局文件比較簡(jiǎn)單,就是一些簡(jiǎn)單的LinearLayout于RelativeLayout的嵌套,這里不再細(xì)說,主要來講解下RefreshHeader.java文件:

private void init(Context context) {
    LayoutParams lp = new LayoutParams(LayoutParams.MATCH_PARENT, 0);
    mContainer = LayoutInflater.from(context).inflate(R.layout.refresh_header, null);
    addView(mContainer, lp);

    rl_my_content = (RelativeLayout) findViewById(R.id.header_content);
    tv_pull_state = (TextView) findViewById(R.id.tv_pull_state);
    iv_arrow = (ImageView) findViewById(R.id.iv_arrow);
    progressBar = (ProgressBar) findViewById(R.id.progressBar);

    rotateUpAnimation = new RotateAnimation(0, -180, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
    rotateUpAnimation.setDuration(180);
    rotateUpAnimation.setFillAfter(true);

    rotateDownAnimation = new RotateAnimation(-180, 0, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
    rotateDownAnimation.setDuration(180);
    rotateDownAnimation.setFillAfter(true);
}
/**
 * 獲取頭部的高度
 * @return
 */
public int getHeaderHeight() {
    return mContainer.getHeight();
}

/**
 * 修改header的高度
 * @param height
 */
public void setHeaderHeight(int height) {
    if (height < 0) height = 0;
    LayoutParams layoutParams = (LayoutParams) mContainer.getLayoutParams();
    layoutParams.height = height;
    mContainer.setLayoutParams(layoutParams);
}

由代碼可以看出,在我們根據(jù)布局文件創(chuàng)建mContainer 時(shí)候,由于xml文件中android:layout_widthandroid:layout_height失效,所以我們需要為其指定了一個(gè)LayoutParams,一是在為之后ListView的addHeader()方法調(diào)用之后能隱藏RefreshHeader,二是寬度讓其匹配父窗體(如果不這么做,控件不居中)。由于執(zhí)行過程中涉及到ImageView的旋轉(zhuǎn)動(dòng)畫效果,我們?yōu)槠渲付▋蓚€(gè)旋轉(zhuǎn)動(dòng)畫,最后再增加獲取和修改RefreshHeader的高度的方法,為后續(xù)操作作準(zhǔn)備。

根據(jù)狀態(tài)改變RefreshHeader界面顯示

初始化操作之后,我們需要根據(jù)狀態(tài)為其設(shè)置相應(yīng)的布局:

/**
 * 根據(jù)狀態(tài)設(shè)置相應(yīng)的布局
 * @param state
 */
public void setHeaderState(int state) {
    if (state == mState && !isFirst) {
        return;
    }
    isFirst = false;
    if (state == REFRESHING) {
        iv_arrow.clearAnimation();
        progressBar.setVisibility(View.VISIBLE);
        iv_arrow.setVisibility(View.INVISIBLE);
        tv_pull_state.setText("正在刷新");
    } else {
        progressBar.setVisibility(View.INVISIBLE);
        iv_arrow.setVisibility(View.VISIBLE);
    }

    switch (state) {
        case PULL_TO_REFRESH:
            if (mState == RELEASE_TO_REFRESH) {
                iv_arrow.clearAnimation();
                iv_arrow.startAnimation(rotateDownAnimation);
            }
            tv_pull_state.setText("下拉刷新");
            break;
        case RELEASE_TO_REFRESH:
            if (mState == PULL_TO_REFRESH) {
                iv_arrow.clearAnimation();
                iv_arrow.startAnimation(rotateUpAnimation);
            }
            tv_pull_state.setText("松開刷新");
            break;

    }
    mState = state;
}

方法邏輯比較簡(jiǎn)單,根據(jù)傳遞進(jìn)來的state不同來改變不同的布局UI,這里需要注意的是,因?yàn)椴季钟杏玫絼?dòng)畫來控制iv_arrow這個(gè)控件,所以每次改變狀態(tài)時(shí)需要執(zhí)行clearAnimation()方法,來消除之前動(dòng)畫效果的影響。

根據(jù)手指拖動(dòng)位置來改變RefreshHeader高度和狀態(tài)

首先我們來看一下項(xiàng)目操作:

執(zhí)行下拉刷新
不執(zhí)行下拉刷新

1.當(dāng)拖動(dòng)位置大于指定高度()時(shí),狀態(tài)改變執(zhí)行setHeaderState()并傳入RELEASE_TO_REFRESH參數(shù)。松開手指后,狀態(tài)改變,執(zhí)行setHeaderState()并傳入REFRESHING參數(shù),位置自動(dòng)滾動(dòng)到相應(yīng)位置。

2.當(dāng)拖動(dòng)位置小于指定高度()時(shí),狀態(tài)改變執(zhí)行setHeaderState()并傳入PULL_TO_REFRESH參數(shù)。松開手指后,狀態(tài)不改變,位置自動(dòng)滾動(dòng)到隱藏位置。

  • 獲取指定高度

初始化高度在RefreshListView初始化方法init()中調(diào)用:

private void init(Context context) {
    mContext = context;

    //初始化mScroller,設(shè)置插值器為減速插值器,隱藏header或者footer時(shí),數(shù)值變化先快后慢
    mScroller = new Scroller(mContext, new DecelerateInterpolator());
    setOnScrollListener(this);

    //初始化header
    mHeader = new RefreshHeader(mContext);
    mHeaderContent = (RelativeLayout) mHeader.findViewById(R.id.header_content);
    this.addHeaderView(mHeader);

    //初始化footer
    mFooter = new RefreshFooter(mContext);
    this.addFooterView(mFooter);

    // 獲取header頭部的固定高度
    ViewTreeObserver observer = mHeader.getViewTreeObserver();
    if (null != observer) {
        observer.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @SuppressWarnings("deprecation")
            @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
            @Override
            public void onGlobalLayout() {
                mHeaderHeight = mHeaderContent.getHeight();

                ViewTreeObserver observer = getViewTreeObserver();
                if (null != observer) {
                    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
                        observer.removeGlobalOnLayoutListener(this);
                    } else {
                        observer.removeOnGlobalLayoutListener(this);
                    }
                }
            }
        });
    }
}
  • 改變RefreshHeader高度和RefreshHeader狀態(tài)

我們知道,RefreshHeader高度是隨著手指的變化而變化,所以重寫RefreshListView的onTouchEvent()方法:

@Override
public boolean onTouchEvent(MotionEvent ev) {
    if (mLastY == -1) {
        mLastY = ev.getRawY();
    }

    switch (ev.getAction()) {
        case MotionEvent.ACTION_DOWN:
            mLastY = ev.getRawY();
            break;
        case MotionEvent.ACTION_MOVE:
            float dy = ev.getRawY() - mLastY;
            mLastY = ev.getRawY();
            if (getFirstVisiblePosition() == 0 && (mHeader.getHeaderHeight() > 0 ||
                    dy > 0)) {
                //OFFSET_RADIO的設(shè)置,讓下拉h(huán)eader時(shí)感覺到阻力,增強(qiáng)體驗(yàn)
                updateHeaderHeight(dy / OFFSET_RADIO);
            }

            if (getLastVisiblePosition() == mTotalItemCount - 1 &&
                    (mFooter.getFooterMargin() > 0 || dy < 0)) {
                //原理同上
                updateFooterHeight(-dy / OFFSET_RADIO);
            }

            break;
        case MotionEvent.ACTION_UP:
            mLastY = ev.getRawY();
            if (getFirstVisiblePosition() == 0) {
                if (!mPullRefreshing && mEnablePullRefresh && (mHeader.getHeaderHeight() > mHeaderHeight)) {
                    refresh();
                }
                resetHeaderHeight();
            }

            if (getLastVisiblePosition() == mTotalItemCount - 1) {
                if (!mPullLoading && mEnableLoadMore && (mFooter.getFooterMargin() > PULL_LOAD_MORE_DELTA)) {
                    loadMore();
                }
                resetFooterHeight();
            }

            break;

    }
    return super.onTouchEvent(ev);
}

注意看:

 if (getFirstVisiblePosition() == 0 && (mHeader.getHeaderHeight() > 0 ||dy > 0)) {
      //OFFSET_RADIO的設(shè)置,讓下拉h(huán)eader時(shí)感覺到阻力,增強(qiáng)體驗(yàn)
      updateHeaderHeight(dy / OFFSET_RADIO);
 }

OFFSET_RADIO是一個(gè)大于1.0的數(shù),通常設(shè)置在1.5到2.0之間,這里取1.8,目的是讓用戶下拉時(shí)RefreshHeader高度的變化比手指拖動(dòng)距離小,給用戶一種下拉阻力的感覺。接下來我們看updateHeaderHeight(dy / OFFSET_RADIO);方法:

private void updateHeaderHeight(float delta) {    
    //改變RefreshHeader高度
    mHeader.setHeaderHeight((int) delta + mHeader.getHeaderHeight());
    //改變RefreshHeader狀態(tài)
    if (mEnablePullRefresh && !mPullRefreshing) {
        if (mHeader.getHeaderHeight() > mHeaderHeight) {
            mHeader.setHeaderState(RefreshHeader.RELEASE_TO_REFRESH);
        } else {
            mHeader.setHeaderState(RefreshHeader.PULL_TO_REFRESH);
        }
    }
    //保證改變RefreshListView高度的同時(shí),不滑動(dòng)RefreshListView
    setSelection(0);
}

代碼根據(jù)傳進(jìn)來的delta值來改變RefreshHeader的界面高度,并將與之前獲取到的mHeaderHeight高度進(jìn)行對(duì)比,來改變RefreshHeader的狀態(tài),進(jìn)而改變RefreshHeader的界面內(nèi)容。需要注意的是這里需要加上setSelection(0);這個(gè)方法。我們?cè)诟割怢istView中找到該方法:

@Override
public void setSelection(int position) {
    setSelectionFromTop(position, 0);
}

跟進(jìn)到setSelectionFromTop(int position, int y)方法中,我們可以在注釋中找到:

Sets the selected item and positions the selection y pixels from the top edgeof the ListView. (If in touch mode, the item will not be selected but it willstill be positioned appropriately.)

該方法的作用就是讓該position上的Item頂?shù)骄嚯xListView上方邊緣y像素的地方,說白了這個(gè)方法的使用避免了你在改變RefreshHeader高度的同時(shí)又滑動(dòng)RefreshListView,避免造成你手指下拉1個(gè)單位同時(shí),RefreshHeader的高度改變1+1/OFFSET_RADIO(非精確)的高度(由于設(shè)置RefreshHeader的高度時(shí)會(huì)把delta強(qiáng)制轉(zhuǎn)換int類型,實(shí)際也不會(huì)這么精確)。setSelection(0);的設(shè)置確保你的RefreshHeader的改變高度是你所期望的1/OFFSET_RADIO手指下拉高度。

根據(jù)手指松開位置再次改變狀態(tài),滾動(dòng)至相應(yīng)位置

看到這里,我們?cè)侔阎皩懙膬删湓拸?fù)制過來:

1.當(dāng)拖動(dòng)位置大于指定高度()時(shí),狀態(tài)改變執(zhí)行setHeaderState()并傳入RELEASE_TO_REFRESH參數(shù)。松開手指后,狀態(tài)改變,執(zhí)行setHeaderState()并傳入REFRESHING參數(shù),位置自動(dòng)滾動(dòng)到相應(yīng)位置。

2.當(dāng)拖動(dòng)位置小于指定高度()時(shí),狀態(tài)改變執(zhí)行setHeaderState()并傳入PULL_TO_REFRESH參數(shù)。松開手指后,狀態(tài)不改變,位置自動(dòng)滾動(dòng)到隱藏位置。

case MotionEvent.ACTION_UP:
    mLastY = ev.getRawY();
    if (getFirstVisiblePosition() == 0) {
        if (!mPullRefreshing && mEnablePullRefresh && (mHeader.getHeaderHeight() > mHeaderHeight)) {
            refresh();
        }
        //RefreshHeader位置自動(dòng)滾動(dòng)到相應(yīng)位置
        resetHeaderHeight();
    }
private void refresh() {
    mPullRefreshing = true;
    if (mEnablePullRefresh && refreshListViewListener != null) {
        mHeader.setHeaderState(RefreshHeader.REFRESHING);
        refreshListViewListener.onRefresh();
    }
}

當(dāng)拖動(dòng)位置大于指定高度()時(shí),松開手指,執(zhí)行refresh()方法,方法中干了兩件事:

  • 改變RefreshHeader的狀態(tài)
  • 將正在刷新狀態(tài)傳遞到外部,讓其去處理

不論最后拖動(dòng)位置如何,最后都要調(diào)用resetHeaderHeight()方法:

private void resetHeaderHeight() {
    int height = mHeader.getHeaderHeight();
    if (height == 0)
        return;

    if (mPullRefreshing && height < mHeaderHeight) {  //正在刷新并且將header向上拖動(dòng)隱藏時(shí)
        return;
    }

    int finalHeight = 0;

    if (mPullRefreshing && mHeader.getHeaderHeight() >= mHeaderHeight) {
        finalHeight = mHeaderHeight;
    }
    mScrollBack = SCROLL_BACK_HEADER;
    mScroller.startScroll(0, height, 0, finalHeight - height, SCROLL_DURATION);

    //激活computeScroll方法
    invalidate();
}

在這個(gè)方法中,我們最終會(huì)調(diào)用mScroller的startScroll方法,通過激活computeScroll()的方法來讓我們的RefreshHeader自動(dòng)滾動(dòng)到我們想要的位置:

@Override
public void computeScroll() {
    if (mScroller.computeScrollOffset()) {
        if (mScrollBack == SCROLL_BACK_HEADER) {
            mHeader.setHeaderHeight(mScroller.getCurrY());
        } else {
            mFooter.setFooterMargin(mScroller.getCurrY());
        }
    }
    super.computeScroll();
}

自此,ListView下拉刷新部分就告一段落。

RefreshFooter實(shí)現(xiàn)

同樣地,編寫RefreshFooter我們可以分為四步來執(zhí)行:

  1. 編寫布局文件
  2. 根據(jù)狀態(tài)改變RefreshFooter界面顯示
  3. 根據(jù)手指拖動(dòng)位置來改變RefreshFooter高度和狀態(tài)
  4. 根據(jù)手指松開位置再次改變狀態(tài),滾動(dòng)至相應(yīng)位置

由于RefreshFooter代碼邏輯與RefreshHeader相似,這里需要注意的是RefreshFooter的初始高度默認(rèn)不是0,目的是使用戶松開手指慣性滑動(dòng)到最底部時(shí),顯示的最后一個(gè)Item時(shí)我們?cè)O(shè)置的RefreshFooter:

mContainer.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));

最后

整體來說,RefreshListView的邏輯整體看起來并不復(fù)雜,只要將其邏輯按照前文說的4個(gè)步驟來實(shí)現(xiàn),化繁為簡(jiǎn),就不會(huì)覺得無從下手。
RefreshListView是我在學(xué)習(xí)android階段一直想寫的一個(gè)項(xiàng)目,借由國(guó)慶期間的幾天時(shí)間,研讀了Github上的XListView以及PullToRefreshListView,趁著最近剛剛學(xué)習(xí),將思路貼上來與大家分享,也算是做一份總結(jié)。如寫得有錯(cuò)誤,歡迎指正~

項(xiàng)目代碼:

https://github.com/MakeDeath/RefreshListView

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,488評(píng)論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,034評(píng)論 3 414
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,327評(píng)論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,554評(píng)論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,337評(píng)論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 54,883評(píng)論 1 321
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,975評(píng)論 3 439
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,114評(píng)論 0 286
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,625評(píng)論 1 332
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,555評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,737評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,244評(píng)論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 43,973評(píng)論 3 345
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,362評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,615評(píng)論 1 280
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,343評(píng)論 3 390
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,699評(píng)論 2 370

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,589評(píng)論 25 707
  • IntelliJ IDEA 15 開發(fā)之前配置jdk ,tomcat8 一:IDEA配置JDK 下載jdk htt...
    Tsnow308閱讀 378評(píng)論 0 0
  • 對(duì)不起 我不知道 該如何想你
    遠(yuǎn)去的星辰呀閱讀 273評(píng)論 1 4
  • 很早很早以前就看過小王子,那時(shí)候窮,20多塊一本薄薄的書都沒法支付,拿著老舊的手機(jī)看著文字版的小王子,那個(gè)時(shí)候的感...
    驀然回首不悔當(dāng)初閱讀 220評(píng)論 0 0