Android自定義A-Z 列表

項目中需要用到A-Z的列表,點擊字母可以滾動到列表中對應(yīng)字母位置葱弟,在百度谷歌上轉(zhuǎn)了一圈壹店,沒看見合適的,又只能自己來了芝加。硅卢。。藏杖。将塑。。蝌麸。点寥。

先看下怎么使用:
xml中:

<com.example.siderquickbar.SiderQuickBarView
        android:id="@+id/siderbar_letters"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:siderQuickItemTypebgColor="#3F88FF"
        app:sliderLetterColor="#808080"
        app:sliderLetterWidth="12sp"
        app:siderQuickItemTypeTextWidth="14sp"
        app:siderQuickItemTypeTextColor="#FFFFFF"/>

java中:

public class MainActivity extends AppCompatActivity {
    private String[] str = new String[]{"一","二","三","死","一","一","一","一",
            "商店","一","重新","我","as","第","是","把","留",
            "阿薩德","as","都是","一","一","商店","商店","發(fā)","一",
            "一","阿薩德","一","商店",};


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        List<ItemBeans> list = new ArrayList<>();
        for(int i=0;i<str.length;i++){
            ItemBeans itemBeans = new ItemBeans();
            itemBeans.setType(i+1);
            itemBeans.setValues(str[i]);
            list.add(itemBeans);
        }

        SiderQuickBarView siderQuickBarView = findViewById(R.id.siderbar_letters);
        siderQuickBarView.setLetters(list)
                         .setOnItemClickListener(letterBean ->
                        Toast.makeText(this,"type==>"+letterBean.getType()+"  value==>"+letterBean.getLetterName(),Toast.LENGTH_SHORT).show()
                );
    }
}

自定義屬性:

 <!--右側(cè)字體顏色-->
        <attr name="sliderLetterColor" format="color"> </attr>
        <!--右側(cè)字體大小-->
        <attr name="sliderLetterWidth" format="dimension"> </attr>
        <!--分類字母背景顏色-->
        <attr name="siderQuickItemTypebgColor" format="color"> </attr>
        <!--分類字母顏色-->
        <attr name="siderQuickItemTypeTextColor" format="color"> </attr>
        <!--分類字母大小-->
        <attr name="siderQuickItemTypeTextWidth" format="dimension"> </attr>
        <!--列表項顏色-->
        <attr name="itemColor" format="color"> </attr>

只需要把任意文字轉(zhuǎn)成list ,按規(guī)則傳入来吩,即可自動按字母排序敢辩,點擊后可返回當前字母和字母對應(yīng)的key值。

QQ圖片20200119200829.jpg
QQ圖片20200119200837.jpg

下面說一下實現(xiàn)的思路:
1.控件由兩部分構(gòu)成:下面的RecyclerView和右側(cè)的自定義字母误褪,使用的是幀布局责鳍。
2.將輸入的文字按字母分類。
3.將分類好的字母放入新的list中兽间,字母和字母對應(yīng)的文字用type進行區(qū)分历葛。
4.為RecyclerView創(chuàng)建適配器,添加點擊事件嘀略。
5.為字母添加點擊事件恤溶,并使RecyclerView滑動。
6.封裝自定義屬性帜羊。

好了咒程,放一下主要代碼:

public class SiderQuickBarView extends FrameLayout {
    private RecyclerView rvLetter;
    private SliderLetterView sliderLetterView;
    private GuideBbar guideBbar;
    private Context context;
    private List<LetterBean> letterBeans;
    private List<CityBean> cityBeans;

    //自定義屬性
    private int siderQuickItemTypebgColor;
    private int siderQuickItemTypeTvColor;
    private float siderQuickItemTypeTextWidth;

    private int sliderLetterColor;
    private int sliderLetterWidth;

    private OnItemClickListener onItemClickListener;

    public SiderQuickBarView(Context context) {
        super(context);
        initView(context);
    }


    public SiderQuickBarView(Context context, AttributeSet attrs) {
        super(context, attrs);
        TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.SiderQuickBarView);
        siderQuickItemTypebgColor = array.getColor(R.styleable.SiderQuickBarView_siderQuickItemTypebgColor, ContextCompat.getColor(context,R.color.colorAccent));
        siderQuickItemTypeTvColor = array.getColor(R.styleable.SiderQuickBarView_siderQuickItemTypeTextColor,ContextCompat.getColor(context,R.color.whilte));
        siderQuickItemTypeTextWidth = array.getDimensionPixelSize(R.styleable.SiderQuickBarView_siderQuickItemTypeTextWidth,20);

        sliderLetterColor = array.getColor(R.styleable.SiderQuickBarView_sliderLetterColor,ContextCompat.getColor(context,R.color.black));
        sliderLetterWidth = array.getDimensionPixelSize(R.styleable.SiderQuickBarView_sliderLetterWidth,50);
        initView(context);
        array.recycle();
    }

    private void initView(Context context) {
        this.context = context;
        //左邊列表
        rvLetter = new RecyclerView(context);
        rvLetter.setLayoutManager(new LinearLayoutManager(context));
        LayoutParams layoutParams = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,LayoutParams.MATCH_PARENT);
        rvLetter.setLayoutParams(layoutParams);

        //右邊字母列表
        sliderLetterView = new SliderLetterView(context,sliderLetterColor,sliderLetterWidth);
        LayoutParams layoutParams1 = new LayoutParams(LayoutParams.WRAP_CONTENT,LayoutParams.WRAP_CONTENT);
        layoutParams1.gravity = Gravity.END;
        layoutParams1.bottomMargin = 10;
        sliderLetterView.setLayoutParams(layoutParams1);

        //GuiBar
        guideBbar = new GuideBbar(context);
        FrameLayout.LayoutParams layoutParams2  = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,ViewGroup.LayoutParams.WRAP_CONTENT);
        layoutParams2.gravity = Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL;
        layoutParams2.bottomMargin = 100;
        guideBbar.setLayoutParams(layoutParams2);

        addView(rvLetter);
        addView(sliderLetterView);
//        addView(guideBbar);
        //字母監(jiān)聽
        setListener();
    }

    private void setListener() {
        sliderLetterView.setOnLetterTouchListener(letter -> {
            for(int i=0;i<letterBeans.size();i++){
                if(letter.equals(letterBeans.get(i).getLetterName())){
                    moveToPosition(i);
                    break;
                }
            }
            Toast.makeText(context,letter,Toast.LENGTH_SHORT).show();
        });

        rvLetter.addOnScrollListener(new RecyclerView.OnScrollListener() {
            @Override
            public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
                super.onScrolled(recyclerView, dx, dy);
                double scrollY = recyclerView.computeVerticalScrollOffset();
                double s = recyclerView.computeVerticalScrollExtent();
                double sum = recyclerView.computeVerticalScrollRange()-s;
                double f = scrollY/sum;
                guideBbar.setGuideX(f);
            }
        });

    }

    private void moveToPosition(int position) {
        if (position != -1) {
            rvLetter.scrollToPosition(position);
            LinearLayoutManager mLayoutManager =
                    (LinearLayoutManager) rvLetter.getLayoutManager();
            assert mLayoutManager != null;
            mLayoutManager.scrollToPositionWithOffset(position, 0);
        }
    }

    public SiderQuickBarView setLetters(List<ItemBeans> letters){
        Map<String, List<ItemBeans>> letterMap = letters.stream().collect(Collectors.groupingBy(letter -> PinYinUtils.getFirstLetter(letter.getValues())));
        Iterator<Map.Entry<String, List<ItemBeans>>> itLetter = letterMap.entrySet().iterator();
        cityBeans = new ArrayList<>();
        letterBeans = new ArrayList<>();
        Iterator<String> keySet = letterMap.keySet().iterator();
        while (keySet.hasNext()){
            CityBean cityBean = new CityBean();
            cityBean.setKey(keySet.next());
            cityBeans.add(cityBean);
        }
        //對字母排序
        SiderComparator siderComparator = new SiderComparator();
        Collections.sort(cityBeans,siderComparator);

        cityBeans.forEach(cityBean -> {
            List<LetterBean> contentList = new ArrayList<LetterBean>();
            List<ItemBeans> itemBeans = letterMap.get(cityBean.getKey());
            LetterBean letterBean = new LetterBean();
            letterBean.setLetterType(LetterType.title);
            letterBean.setLetterName(cityBean.getKey());
            contentList.add(letterBean);
            itemBeans.forEach(item -> {
                LetterBean letterBean1 = new LetterBean();
                letterBean1.setLetterType(LetterType.contet);
                letterBean1.setType(item.getType());
                letterBean1.setLetterName(item.getValues());
                contentList.add(letterBean1);
            });
            cityBean.setBeanList(contentList);
        });

        cityBeans.forEach(cityBean -> {
            letterBeans.addAll(cityBean.getBeanList());
        });
        LetterListAdapter letterListAdapter = new LetterListAdapter(context, letterBeans, siderQuickItemTypeTvColor, siderQuickItemTypebgColor, siderQuickItemTypeTextWidth);
        rvLetter.setAdapter(letterListAdapter);
        letterListAdapter.setOnItemClickListener((adapter, position) -> {
            LetterListAdapter la = (LetterListAdapter) adapter;
            LetterBean item = la.getItem(position);
            if(onItemClickListener != null){
                onItemClickListener.onClick(item);
            }

        });
        return this;
    }

    interface OnItemClickListener{
        void onClick(LetterBean letterBean);
    }

    public SiderQuickBarView setOnItemClickListener(OnItemClickListener onItemClickListener){
        this.onItemClickListener = onItemClickListener;
        return  this;
    }


    class SiderComparator implements Comparator<CityBean> {

        @Override
        public int compare(CityBean cityBean1, CityBean cityBean2) {
            return cityBean1.getKey().compareTo(cityBean2.getKey());
        }
    }


    @Retention(RetentionPolicy.SOURCE)
    @IntDef({LetterType.title,LetterType.contet})
    public @interface LetterType{
        int title = 0;
        int contet = 1;
    }
}

這個就是整個控件了,繼承自FrameLayout讼育,包含一個RecyclerView和右邊的自定義字母(忽略被注釋的View)

這里最主要的就是將輸入的字符串信息帐姻,按照字母分類,我這里用到了一個第三庫:

 implementation 'com.belerweb:pinyin4j:2.5.1'

外加這個庫的一個工具類:

public class PinYinUtils {
    /**
     * 將字符串中的中文轉(zhuǎn)化為拼音,其他字符不變
     *
     * @param inputString
     * @return
     */
    public static String getPingYin(String inputString) {
        HanyuPinyinOutputFormat format = new HanyuPinyinOutputFormat();
        format.setCaseType(HanyuPinyinCaseType.LOWERCASE);
        format.setToneType(HanyuPinyinToneType.WITHOUT_TONE);
        format.setVCharType(HanyuPinyinVCharType.WITH_V);

        char[] input = inputString.trim().toCharArray();
        String output = "";
        try {
            for (int i = 0; i < input.length; i++) {
                if (java.lang.Character.toString(input[i]).matches("[\\u4E00-\\u9FA5]+")) {
                    String[] temp = PinyinHelper.toHanyuPinyinStringArray(input[i], format);
                    output += temp[0];
                } else {
                    output += java.lang.Character.toString(input[i]);
                }
            }
        } catch (BadHanyuPinyinOutputFormatCombination e) {
            e.printStackTrace();
        }
        return output;
    }
    /**
     * 獲取漢字串拼音首字母奶段,英文字符不變
     * @param chinese 漢字串
     * @return 漢語拼音首字母
     */
    public static String getFirstSpell(String chinese) {
        StringBuffer pybf = new StringBuffer();
        char[] arr = chinese.toCharArray();
        HanyuPinyinOutputFormat defaultFormat = new HanyuPinyinOutputFormat();
        defaultFormat.setCaseType(HanyuPinyinCaseType.LOWERCASE);
        defaultFormat.setToneType(HanyuPinyinToneType.WITHOUT_TONE);
        for (int i = 0; i < arr.length; i++) {
            if (arr[i] > 128) {
                try {
                    String[] temp = PinyinHelper.toHanyuPinyinStringArray(arr[i], defaultFormat);
                    if (temp != null) {
                        pybf.append(temp[0].charAt(0));
                    }
                } catch (BadHanyuPinyinOutputFormatCombination e) {
                    e.printStackTrace();
                }
            } else {
                pybf.append(arr[i]);
            }
        }
        return pybf.toString().replaceAll("\\W", "").trim();
    }
    /**
     * 獲取漢字串拼音饥瓷,英文字符不變
     * @param chinese 漢字串
     * @return 漢語拼音
     */
    public static String getFullSpell(String chinese) {
        StringBuffer pybf = new StringBuffer();
        char[] arr = chinese.toCharArray();
        HanyuPinyinOutputFormat defaultFormat = new HanyuPinyinOutputFormat();
        defaultFormat.setCaseType(HanyuPinyinCaseType.LOWERCASE);
        defaultFormat.setToneType(HanyuPinyinToneType.WITHOUT_TONE);
        for (int i = 0; i < arr.length; i++) {
            if (arr[i] > 128) {
                try {
                    pybf.append(PinyinHelper.toHanyuPinyinStringArray(arr[i], defaultFormat)[0]);
                } catch (BadHanyuPinyinOutputFormatCombination e) {
                    e.printStackTrace();
                }
            } else {
                pybf.append(arr[i]);
            }
        }
        return pybf.toString();
    }

    /** 獲取首字母
     * @param letter
     * @return
     */
    public static String getFirstLetter(String letter){
        StringBuffer pybf = new StringBuffer();
        char[] arr = letter.toCharArray();
        HanyuPinyinOutputFormat defaultFormat = new HanyuPinyinOutputFormat();
        defaultFormat.setCaseType(HanyuPinyinCaseType.LOWERCASE);
        defaultFormat.setToneType(HanyuPinyinToneType.WITHOUT_TONE);
        for (int i = 0; i < arr.length; i++) {
            if (arr[i] > 128) {
                try {
                    pybf.append(PinyinHelper.toHanyuPinyinStringArray(arr[i], defaultFormat)[0]);
                } catch (BadHanyuPinyinOutputFormatCombination e) {
                    e.printStackTrace();
                }
            } else {
                pybf.append(arr[i]);
            }
        }
        return pybf.substring(0,1);
    }
}

這個工具類的作用,可以將文字的首字母給取出來痹籍, 那么取出來干嘛呢呢铆??
下面我正好借助lamda表達式蹲缠,根據(jù)首字母將數(shù)據(jù)進行分組:

Map<String, List<ItemBeans>> letterMap = letters.stream().collect(Collectors.groupingBy(letter -> PinYinUtils.getFirstLetter(letter.getValues())));

分完組之后的數(shù)據(jù)就是一個map集合 map的key就是 各數(shù)據(jù)對應(yīng)的首字母棺克,value就是各首字母對應(yīng)的數(shù)據(jù)悠垛,再將map中的數(shù)據(jù),放入一個新的list中娜谊,所以現(xiàn)在混亂數(shù)據(jù)就變成了一組組有序集合了确买,但現(xiàn)在還不夠,因為是按照A-Z排序因俐,目前不是按A-Z拇惋,可能是g周偎,f抹剩,a,d 這樣亂
排序的蓉坎,只是把數(shù)據(jù)都放在了它們各自的首字母下面而已澳眷,那么,下面就借助Comparator這個接口讓字母按照A-Z的順序排序:

class SiderComparator implements Comparator<CityBean> {

        @Override
        public int compare(CityBean cityBean1, CityBean cityBean2) {
            return cityBean1.getKey().compareTo(cityBean2.getKey());
        }
    }

        //對字母排序
        SiderComparator siderComparator = new SiderComparator();
        Collections.sort(cityBeans,siderComparator);

排序完成之后蛉艾,這個list就可以像普通的list一樣放入RecyclerView的適配器中使用了钳踊。

下面看一下自定義的字母:

/**
 * 右邊字母自定義
 * @author amggy
 */
public class SliderLetterView extends View {
    private Paint paint;
    private String[] letters;
    private double itemHeight;
    private int position;
    private OnLetterTouchListener onLetterTouchListener;

    private int paintColor;
    private int paintStrokWith;

    public SliderLetterView(Context context,int paintColor,int paintStrokWith) {
        super(context);
        this.paintColor = paintColor;
        this.paintStrokWith = paintStrokWith;
        initView(context);
    }

    public SliderLetterView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        initView(context);

    }

    public SliderLetterView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initView(context);
    }

    private void initView(Context context) {
        letters = new String[]{"a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z"};
        position = 0;
        paint = new Paint();
        paint.setAntiAlias(true);
        paint.setDither(true);
        paint.setColor(paintColor);
        paint.setTextSize(paintStrokWith);
        paint.setTextAlign(Paint.Align.CENTER);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        double screenHeight = getHeight();
        itemHeight = screenHeight /letters.length;
        float letterX = (float)getWidth()/2;
        for(int i=0;i<letters.length;i++){
            canvas.drawText(letters[i],letterX,(float) ((i+1)*itemHeight),paint);
        }
    }

    @SuppressLint("ClickableViewAccessibility")
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                float eventY = event.getY();
                position = (int) (eventY/itemHeight);
                if(onLetterTouchListener != null){
                    onLetterTouchListener.onTouch(letters[position]);
                }
                break;
            case MotionEvent.ACTION_MOVE:
                break;
            case MotionEvent.ACTION_UP:
                break;
            default:
                break;
        }
        return true;
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int with = MeasureSpec.getSize(widthMeasureSpec);
        int withMode = MeasureSpec.getMode(widthMeasureSpec);

        int high = MeasureSpec.getSize(heightMeasureSpec);
        int highMode = MeasureSpec.getMode(heightMeasureSpec);

        int w = 0;
        int h = 0;

        switch(withMode){
            case MeasureSpec.EXACTLY:
                w = with;
                break;
            case MeasureSpec.AT_MOST:
                w = 80;
                break;
            default:
                break;
        }

        switch(highMode){
            case MeasureSpec.EXACTLY:
                h = high;
                break;
            case MeasureSpec.AT_MOST:
                h = getMeasuredHeight();
                break;
            default:
                break;
        }
        setMeasuredDimension(w,h);
    }


    public void setOnLetterTouchListener(OnLetterTouchListener onLetterTouchListener){
        this.onLetterTouchListener = onLetterTouchListener;
    }

    public interface OnLetterTouchListener{
        /** 返回選中字母
         * @param letter 字母
         */
        void onTouch(String letter);
    }
}

自定義字母繼承自View,按照控件的高度勿侯,等分的繪制字母拓瞪,添加onTouch事件,將觸碰到的字母返回出去助琐,給RecyclerView滾動到指定位置做準備祭埂。

思路大概就是這個樣子,只要思路是對的兵钮,東西就好寫了蛆橡。。掘譬。泰演。完

慢。提前祝各位新年快樂4行D阑馈!靴拱!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末垃喊,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子缭嫡,更是在濱河造成了極大的恐慌以蕴,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,386評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件壶运,死亡現(xiàn)場離奇詭異,居然都是意外死亡笤成,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,142評論 3 394
  • 文/潘曉璐 我一進店門眷茁,熙熙樓的掌柜王于貴愁眉苦臉地迎上來炕泳,“玉大人,你說我怎么就攤上這事上祈∨嘧瘢” “怎么了?”我有些...
    開封第一講書人閱讀 164,704評論 0 353
  • 文/不壞的土叔 我叫張陵登刺,是天一觀的道長籽腕。 經(jīng)常有香客問我,道長纸俭,這世上最難降的妖魔是什么皇耗? 我笑而不...
    開封第一講書人閱讀 58,702評論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮揍很,結(jié)果婚禮上郎楼,老公的妹妹穿的比我還像新娘。我一直安慰自己窒悔,他們只是感情好呜袁,可當我...
    茶點故事閱讀 67,716評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著简珠,像睡著了一般阶界。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上北救,一...
    開封第一講書人閱讀 51,573評論 1 305
  • 那天荐操,我揣著相機與錄音,去河邊找鬼珍策。 笑死托启,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的攘宙。 我是一名探鬼主播屯耸,決...
    沈念sama閱讀 40,314評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼蹭劈!你這毒婦竟也來了疗绣?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,230評論 0 276
  • 序言:老撾萬榮一對情侶失蹤铺韧,失蹤者是張志新(化名)和其女友劉穎多矮,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,680評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡塔逃,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,873評論 3 336
  • 正文 我和宋清朗相戀三年讯壶,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片湾盗。...
    茶點故事閱讀 39,991評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡伏蚊,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出格粪,到底是詐尸還是另有隱情躏吊,我是刑警寧澤,帶...
    沈念sama閱讀 35,706評論 5 346
  • 正文 年R本政府宣布帐萎,位于F島的核電站比伏,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏吓肋。R本人自食惡果不足惜凳怨,卻給世界環(huán)境...
    茶點故事閱讀 41,329評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望是鬼。 院中可真熱鬧,春花似錦紫新、人聲如沸均蜜。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,910評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽囤耳。三九已至,卻和暖如春偶芍,著一層夾襖步出監(jiān)牢的瞬間充择,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,038評論 1 270
  • 我被黑心中介騙來泰國打工匪蟀, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留椎麦,地道東北人。 一個月前我還...
    沈念sama閱讀 48,158評論 3 370
  • 正文 我出身青樓材彪,卻偏偏與公主長得像观挎,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子段化,可洞房花燭夜當晚...
    茶點故事閱讀 44,941評論 2 355