RecycleView的左滑實(shí)現(xiàn)

RecycleView的左滑實(shí)現(xiàn)

最終的效果圖是這樣的

swap2.gif
swap3.gif

要實(shí)現(xiàn)這樣的一個(gè)效果惭适,用到的關(guān)鍵技術(shù):
自定義view的基本知識(shí)+事件處理+其它知識(shí)

一.右邊的操作view

1.數(shù)據(jù)的組裝

我們可以把右邊的操作選項(xiàng)抽象出來數(shù)據(jù)對(duì)象即可,對(duì)于老司機(jī)的你們一看就懂。

public class SwipeMenuItem {

    private static final int TITLE_SIZE = 20;//sp
    private static final int WIDTH = 80;//dp
    private int id;
    private Context mContext;
    private String title;
    private Drawable icon;
    private Drawable background;
    private int titleColor;
    private int titleSize;
    private int width;

    public SwipeMenuItem(Context context) {
        mContext = context;
        //設(shè)置默認(rèn)值
        DisplayMetrics dm = mContext.getResources().getDisplayMetrics();
        titleColor = Color.WHITE;
        titleSize = TITLE_SIZE;
        width = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, WIDTH, dm);
    }
   }

2.SwipeMenuView的簡(jiǎn)單擴(kuò)展(自定義view的一種吧)

public class SwipeMenuView extends LinearLayout implements View.OnClickListener {

    private SwipeMenuLayout mLayout;
    private SwipeMenu mMenu;
    private OnMenuItemClickListener mOnMenuItemClickListener;
    private int position;

    public int getPosition() {
        return position;
    }

    public void setPosition(int position) {
        this.position = position;
    }

    public SwipeMenuView(SwipeMenu menu) {
        super(menu.getContext());
        setOrientation(LinearLayout.HORIZONTAL);
        mMenu = menu;
        List<SwipeMenuItem> items = mMenu.getMenuItems();
        int id = 0;
        for (SwipeMenuItem item : items) {
            addItem(item, id++);
        }
    }

    private void addItem(SwipeMenuItem item, int id) {
        LayoutParams params = new LayoutParams(item.getWidth(),
                LayoutParams.MATCH_PARENT);
        LinearLayout parent = new LinearLayout(getContext());
        parent.setId(id);
        parent.setGravity(Gravity.CENTER);
        parent.setOrientation(LinearLayout.VERTICAL);
        parent.setLayoutParams(params);
        parent.setBackgroundDrawable(item.getBackground());
        parent.setOnClickListener(this);
        addView(parent);

        if (item.getIcon() != null) {
            parent.addView(createIcon(item));
        }
        if (!TextUtils.isEmpty(item.getTitle())) {
            parent.addView(createTitle(item));
        }

    }

    private ImageView createIcon(SwipeMenuItem item) {
        ImageView iv = new ImageView(getContext());
        iv.setImageDrawable(item.getIcon());
        return iv;
    }

    private TextView createTitle(SwipeMenuItem item) {
        TextView tv = new TextView(getContext());
        tv.setText(item.getTitle());
        tv.setGravity(Gravity.CENTER);
        tv.setTextSize(item.getTitleSize());
        tv.setTextColor(item.getTitleColor());
        return tv;
    }

    @Override
    public void onClick(View v) {
        if (mOnMenuItemClickListener != null && mLayout.isOpen()) {
            mOnMenuItemClickListener.onMenuItemClick(position, mMenu, v.getId());
        }
    }

    public interface OnMenuItemClickListener {
        void onMenuItemClick(int position, SwipeMenu menu, int index);
    }

    public void setOnMenuItemClickListener(
            OnMenuItemClickListener mOnMenuItemClickListener) {
        this.mOnMenuItemClickListener = mOnMenuItemClickListener;
    }

    public void setLayout(SwipeMenuLayout mLayout) {
        this.mLayout = mLayout;
    }
} 

說白了就是繼承LinearLayout 加了一個(gè)回調(diào)接口绣否,對(duì)于老司機(jī)的你們一看又懂了悲关。對(duì)于SwipeMenuLayout是什么怜校,我們后面會(huì)講的,別著急嗎颜屠?嘻嘻

二.RecyclerView.Adapter的處理

  • 我們本著在不影響用戶原有的adapter的基礎(chǔ)上盡量不改或者少改。
    對(duì)于RecyclerView的Adapter 我們都是繼承RecyclerView.Adapter鹰祸。<br />
  • 主要是重寫onCreateViewHolder和onBindViewHolder方法甫窟。
  • 對(duì)于onBindViewHolder方法完美不錯(cuò)任何處理,也沒有必要做蛙婴。<br />
  • 主要是onCreateViewHolder方法粗井,這個(gè)方法返回是一條item的布局ui,對(duì)于我們這個(gè)效果在不改動(dòng)優(yōu)惠正常的view布局的情況下街图,我們可以這么做呢浇衬??餐济?耘擂?<br />
  • 咦! 我們可以在原來的基礎(chǔ)上再套一層FrameLayout. 是的絮姆,沒錯(cuò)醉冤,老司機(jī)!篙悯!
  @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        //根據(jù)數(shù)據(jù)創(chuàng)建右邊的操作view
        SwipeMenuView menuView = swipeMenuBuilder.create();
        //包裝用戶的item布局
        SwipeMenuLayout swipeMenuLayout = SwapWrapperUtils.wrap(parent, R.layout.item, menuView, new BounceInterpolator(), new LinearInterpolator());
        MyViewHolder holder = new MyViewHolder(swipeMenuLayout);
        setListener(parent, holder, viewType);
        return holder;
    }

SwapWrapperUtils.wrap 這個(gè)方法這里就不說了就是LayoutInflater加載布局蚁阳。

三.SwipeMenuLayout-view的設(shè)計(jì)

繼承自FrameLayout

講用戶的itemview這里我們叫Contentview,以及操作view我們叫MenuView鸽照,添加到這個(gè)FrameLayout上

   setLayoutParams(new ViewGroup.LayoutParams(LayoutParams.MATCH_PARENT,
                LayoutParams.WRAP_CONTENT));
        mMenuView.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT,
                LayoutParams.WRAP_CONTENT));
        addView(mContentView);
        addView(mMenuView);
設(shè)置初始狀態(tài)

我們要測(cè)量menuview的寬螺捐,高度就是Contentview的高。
我們要布局menuview移宅,在Contentview的右側(cè)归粉。
如圖:

layout.png
   @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        //測(cè)量mMenuView的寬,高為mContentView的高
        mMenuView.measure(MeasureSpec.makeMeasureSpec(0,
                MeasureSpec.UNSPECIFIED), MeasureSpec.makeMeasureSpec(
                getMeasuredHeight(), MeasureSpec.EXACTLY));
    }


    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        mContentView.layout(0, 0, getMeasuredWidth(),
                mContentView.getMeasuredHeight());
        //在mContentView的右側(cè)
        mMenuView.layout(getMeasuredWidth(), 0,
                getMeasuredWidth() + mMenuView.getMeasuredWidth(),
                mContentView.getMeasuredHeight());
    }
控制滑動(dòng)

在android中根據(jù)滑動(dòng)來控制view有好多種,這里我們用layout方法
主要就是在recycleview滑動(dòng)時(shí)找到其中一條的位置position在ontouch方法中合適的時(shí)機(jī)將事件傳到該view上漏峰。什么時(shí)候觸發(fā)這個(gè)方法呢
糠悼,下文會(huì)說recycleview的處理事件。
我們寫一個(gè)方法將事件傳遞到此view上來控制menuView和contentView

    public void  onSwipe(MotionEvent event) {
        mGestureDetector.onTouchEvent(event);
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                mDownX = (int) event.getX();
                isFling = false;
                break;
            case MotionEvent.ACTION_MOVE:
                //按下去-當(dāng)前的位置
                int dis = (int) (mDownX - event.getX());
                //menuView打開狀態(tài)dis+mMenuView寬
                if (state == STATE_OPEN) {
                    dis += mMenuView.getWidth();
                }
                swipe(dis);
                break;
            case MotionEvent.ACTION_UP:
                //快速滑動(dòng),或者超過了mMenuView寬的一半則打開,否則關(guān)閉
                if (isFling || (mDownX - event.getX()) > (mMenuView.getWidth() / 2)) {
                    smoothOpenMenu();
                } else {
                    smoothCloseMenu();
                }
                break;
        }
    }

    /**
     * 更改位置
     * @param dis dis
     */
    private void swipe(int dis) {
        //mContentView的最大為mMenuView的寬
        if (dis > mMenuView.getWidth()) {
            dis = mMenuView.getWidth();
        }
        //mContentView-left的最小值為0即正常值
        if (dis < 0) {
            dis = 0;
        }
        //設(shè)置完mContentView的left就可以得出right以及mMenuView的left和right了
        //主要是left,right
        //left 最大值為-mMenuView.getWidth()
        mContentView.layout(-dis, mContentView.getTop(),
                mContentView.getWidth() - dis, getMeasuredHeight());
        mMenuView.layout(mContentView.getWidth() - dis, mMenuView.getTop(),
                mContentView.getWidth() + mMenuView.getWidth() - dis,
                mMenuView.getBottom());
    }
打開與關(guān)閉

借助computeScroll方法來不停的layout設(shè)置位置浅乔,代碼都對(duì)于位置的計(jì)算有注釋倔喂,生怕解釋不清楚铝条。

   
    @Override
    public void computeScroll() {
        //讓mMenuView打開
        if (state == STATE_OPEN) {
            //是否停止了滑動(dòng)
            if (mOpenScroller.computeScrollOffset()) {
                swipe(mOpenScroller.getCurrX());
                //重繪UI
                postInvalidate();
            }
        } else {//讓mMenuView關(guān)閉
               //mContentView的
            if (mCloseScroller.computeScrollOffset()) {
                //mBaseX為當(dāng)前的mContentView的left,可以結(jié)合
                swipe(mBaseX - mCloseScroller.getCurrX());
                postInvalidate();
            }
        }
    }

    /**
     * 平滑的關(guān)閉mMenuView
     */
    public void smoothCloseMenu() {
        state = STATE_CLOSE;
        mBaseX = -mContentView.getLeft();
        //關(guān)閉是我們要讓mContentView的慢慢的減小,
        //mCloseScroller.getCurrX()的范圍是(0,mBaseX)
        mCloseScroller.startScroll(0, 0, mBaseX, 0, DURATION);
        postInvalidate();
    }
    /**
     * 平滑的打開mMenuView
     */
    public void smoothOpenMenu() {
        state = STATE_OPEN;
        //其實(shí)我們這里是用到了Scroller類產(chǎn)生的值(當(dāng)然借助Interpolator來實(shí)現(xiàn)不同的值漸變,從而實(shí)現(xiàn)不同的效果)
        //打開的時(shí)候mContentView的left從當(dāng)前的-mContentView.getLeft()到mMenuView.getWidth()
        //在computeScroll方法中 swipe(mOpenScroller.getCurrX());即可
        //mOpenScroller.getCurrX()的范圍是(-mContentView.getLeft(),mMenuView.getWidth())
        //-mContentView.getLeft()為正值
        mOpenScroller.startScroll(-mContentView.getLeft(), 0,
                mMenuView.getWidth(), 0, DURATION);
        postInvalidate();
    }

四.RecyclerView的事件處理

首先我們要明白一點(diǎn)就是:我們要不影響用戶原來的item的點(diǎn)擊與長(zhǎng)按等事件。

我們肯定要重新事件的攔截與處理方法席噩。即onInterceptTouchEvent
與onTouchEvent方法班缰。我們需要在這2個(gè)方法里做如下的處理。

  1. 找到按下去的那一條
  2. 什么時(shí)候攔截各種down悼枢,move埠忘,up事件
  3. 處理各種down,move馒索,up事件
找到按下去的那一條
//找到當(dāng)前點(diǎn)擊坐標(biāo)下的所處于SwapRecyclerView的位置

                int mFirstPosition = ((LinearLayoutManager) getLayoutManager()).findFirstVisibleItemPosition();
                int count = getChildCount();
                for (int i = 0; i < count; i++) {
                    final View child = getChildAt(i);
                    if (child.getVisibility() == View.VISIBLE) {
                        child.getHitRect(mTouchFrame);
                        //判斷是否點(diǎn)擊到該控件上
                        if (mTouchFrame.contains(x, y)) {
                            mTouchPosition = mFirstPosition + i;
                            break;
                        }
                    }
                }

找到了pos位置就可以 View view = getChildAt(mTouchPosition - mFirstPosition);
來獲取那個(gè)view了莹妒,就可以進(jìn)行事件的處理了。
child.getHitRect方法 绰上,我們看下sdkapi的注釋:

  /**
  找到控件占據(jù)的矩形區(qū)域的矩形坐標(biāo)
     * Hit rectangle in parent's coordinates
     *
     返回的矩形   控件占據(jù)的矩形區(qū)域
     * @param outRect The hit rectangle of the view.
     */
    public void getHitRect(Rect outRect) {
        if (hasIdentityMatrix() || mAttachInfo == null) {
            outRect.set(mLeft, mTop, mRight, mBottom);
        } else {
            final RectF tmpRect = mAttachInfo.mTmpTransformRect;
            tmpRect.set(0, 0, getWidth(), getHeight());
            getMatrix().mapRect(tmpRect); // TODO: mRenderNode.mapRect(tmpRect)
            outRect.set((int) tmpRect.left + mLeft, (int) tmpRect.top + mTop,
                    (int) tmpRect.right + mLeft, (int) tmpRect.bottom + mTop);
        }
    }
onInterceptTouchEvent 攔截 onTouch的處理 的搞基生活

down攔截的時(shí)候:

  1. menuView處于打開且點(diǎn)擊的不在menu區(qū)域
  2. 達(dá)到了滑動(dòng)的臨界值
  3. 這些情況都要交要我們處理旨怠,需要攔截(reutrn true),交給ontouch方法
  //找到了
                if (mTouchPosition != -1) {
                    //通過position得到item的viewHolder,并判斷合法性
                    View view = getChildAt(mTouchPosition - mFirstPosition);
                    RecyclerView.ViewHolder viewHolder = getChildViewHolder(view);
                    if (viewHolder.itemView instanceof SwipeMenuLayout) {
                        //menuView處于打開且點(diǎn)擊的不在menu區(qū)域
                        if (mTouchView != null && mTouchView.isOpen() && !inRangeOfView(mTouchView.getmMenuView(), event)) {
                            //攔截事件,交給自己的onTouch方法處理.
                            return true;
                        }
                        mTouchView = (SwipeMenuLayout) view;
                    } else {
                        throw new RuntimeException("viewHolder.itemView  must be SwipeMenuLayout layout");
                    }
                    //將事件交給SwipeMenuLayout處理down事件
                    mTouchView.onSwipe(event);
                }
                //down事件,如果沒有打開menu,則不攔截,仍然交給系統(tǒng)
                return handled;

然后在onTouchEven方法里處理down:

 case MotionEvent.ACTION_DOWN:
                //如果當(dāng)前是處于打開的且用戶按下去正好是打開menu的那行
                if (mTouchPosition == oldPos && mTouchView != null
                        && mTouchView.isOpen()) {
                    mTouchState = TOUCH_STATE_X;
                    mTouchView.onSwipe(event);
                    return true;
                } else {
                    //如果不是直接關(guān)閉
                    if (mTouchView != null && mTouchView.isOpen()) {
                        mTouchView.smoothCloseMenu();
                        mTouchView = null;
                        return super.onTouchEvent(event);
                    }
                }
                break;

move攔截的時(shí)候:
達(dá)到滑動(dòng)的臨界值就可以攔截了return true了蜈块。
mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();

   case MotionEvent.ACTION_MOVE:
               float dy = Math.abs((event.getY() - mDownY));
               float dx = Math.abs((event.getX() - mDownX));
               //達(dá)到了滑動(dòng)的臨界值
               if (Math.abs(dy) > mTouchSlop || Math.abs(dx) > mTouchSlop) {
                   if (mTouchState == TOUCH_STATE_NONE) {
                       if (Math.abs(dy) > mTouchSlop) {//上下滑動(dòng)的
                           mTouchState = TOUCH_STATE_Y;
                       } else if (dx > mTouchSlop) {//左右滑動(dòng)的
                           mTouchState = TOUCH_STATE_X;
                           if (mOnSwipeListener != null) {
                               mOnSwipeListener.onSwipeStart(mTouchPosition);
                           }
                       }
                   }
                   return true;//攔截事件,交給自己的onTouch方法處理.
               }

然后在onTouchEven方法里處理move:如果是左右我們才處理鉴腻,否則拜拜了您。

case MotionEvent.ACTION_MOVE:
             //左右滑動(dòng)交給mTouchView處理,事件消費(fèi)了
             if (mTouchState == TOUCH_STATE_X) {
                 if (mTouchView != null) {
                     mTouchView.onSwipe(event);
                 }
                 event.setAction(MotionEvent.ACTION_CANCEL);
                 super.onTouchEvent(event);
                 return true;
             }
             break;

最后up事件就簡(jiǎn)單了不需要攔截百揭,無非就是TOUCH_STATE_X狀態(tài)交給我們之前的SwipeMenuLayout處理打開還是關(guān)閉 爽哎, 以及 將一些變量的恢復(fù)為初始化狀態(tài)。
到此整個(gè)實(shí)現(xiàn)就完了信峻。

這里只分析一些核心的關(guān)鍵技術(shù)倦青,其它的都能看懂。

代碼下載地址:

https://github.com/ta893115871/SwapMenuRecyclerView

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末盹舞,一起剝皮案震驚了整個(gè)濱河市产镐,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌踢步,老刑警劉巖癣亚,帶你破解...
    沈念sama閱讀 211,743評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異获印,居然都是意外死亡述雾,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,296評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門兼丰,熙熙樓的掌柜王于貴愁眉苦臉地迎上來玻孟,“玉大人,你說我怎么就攤上這事鳍征∈螋幔” “怎么了?”我有些...
    開封第一講書人閱讀 157,285評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵艳丛,是天一觀的道長(zhǎng)匣掸。 經(jīng)常有香客問我趟紊,道長(zhǎng),這世上最難降的妖魔是什么碰酝? 我笑而不...
    開封第一講書人閱讀 56,485評(píng)論 1 283
  • 正文 為了忘掉前任霎匈,我火速辦了婚禮,結(jié)果婚禮上送爸,老公的妹妹穿的比我還像新娘铛嘱。我一直安慰自己,他們只是感情好碱璃,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,581評(píng)論 6 386
  • 文/花漫 我一把揭開白布弄痹。 她就那樣靜靜地躺著,像睡著了一般嵌器。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上谐丢,一...
    開封第一講書人閱讀 49,821評(píng)論 1 290
  • 那天爽航,我揣著相機(jī)與錄音,去河邊找鬼乾忱。 笑死讥珍,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的窄瘟。 我是一名探鬼主播衷佃,決...
    沈念sama閱讀 38,960評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼蹄葱!你這毒婦竟也來了氏义?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,719評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤图云,失蹤者是張志新(化名)和其女友劉穎惯悠,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體竣况,經(jīng)...
    沈念sama閱讀 44,186評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡克婶,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,516評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了丹泉。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片情萤。...
    茶點(diǎn)故事閱讀 38,650評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖摹恨,靈堂內(nèi)的尸體忽然破棺而出筋岛,到底是詐尸還是另有隱情,我是刑警寧澤睬塌,帶...
    沈念sama閱讀 34,329評(píng)論 4 330
  • 正文 年R本政府宣布泉蝌,位于F島的核電站歇万,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏勋陪。R本人自食惡果不足惜贪磺,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,936評(píng)論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望诅愚。 院中可真熱鬧寒锚,春花似錦、人聲如沸违孝。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,757評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽雌桑。三九已至喇喉,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間校坑,已是汗流浹背拣技。 一陣腳步聲響...
    開封第一講書人閱讀 31,991評(píng)論 1 266
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留耍目,地道東北人膏斤。 一個(gè)月前我還...
    沈念sama閱讀 46,370評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像邪驮,于是被迫代替她去往敵國(guó)和親莫辨。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,527評(píng)論 2 349

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

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,782評(píng)論 25 707
  • 什么是View View 是 Android 中所有控件的基類毅访。 View的位置參數(shù) View 的位置由它的四個(gè)頂...
    acc8226閱讀 1,154評(píng)論 0 7
  • 簡(jiǎn)介: 提供一個(gè)讓有限的窗口變成一個(gè)大數(shù)據(jù)集的靈活視圖沮榜。 術(shù)語表: Adapter:RecyclerView的子類...
    酷泡泡閱讀 5,148評(píng)論 0 16
  • 1 概述 當(dāng)Android系統(tǒng)捕獲到觸摸事件后,如何準(zhǔn)確地傳遞給真正需要這個(gè)事件的View呢俺抽?Android系統(tǒng)給...
    小蕓論閱讀 5,019評(píng)論 2 38
  • 從名字上看敞映,是一本有點(diǎn)成功學(xué)、有點(diǎn)雞湯的書磷斧。 內(nèi)容大多也是老生常談嘛振愿,比如通過類比來強(qiáng)化記憶,通過練習(xí)來鞏固知識(shí)弛饭,...
    an0nym0us閱讀 837評(píng)論 0 1