1. 程式人生 > >Android自定義控制元件BannerLayout,實現廣告輪播

Android自定義控制元件BannerLayout,實現廣告輪播

Android自定義廣告輪播圖

自定義的BannerLayout,通過ViewPager來實現,配合Glide 實現本地以及網路圖片的載入。

效果:

專案結構:


  1. 在build.gradle中新增對Glide (圖片載入框架)的引用:

compile 'com.github.bumptech.glide:glide:3.7.0'

 2.AndroidManifest.xml中開網路許可權

<uses-permissionandroid:name="android.permission.INTERNET"/>

3.BannerLayout檔案

package com.example.zhh.mytestbanner;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.LayerDrawable;
import android.os.Handler;
import android.os.Message;
import android.support.annotation.NonNull;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewPager;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.Interpolator;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.Scroller;
import android.widget.TextView;

import com.bumptech.glide.Glide;
import com.squareup.picasso.Picasso;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;

/**
 * 輪播圖元件
 * Created by TwoSX on 2016/3/30.
 */
public class BannerLayout extends RelativeLayout {

    private ViewPager mViewPager; // 輪播容器

    // 指示器(圓點)容器
    private LinearLayout indicatorContainer;

    private Drawable unSelectedDrawable;
    private Drawable selectedDrawable;


    private int WHAT_AUTO_PLAY = 1000;

    private boolean isAutoPlay = true; // 自動輪播

    private int itemCount;

    private int selectedIndicatorColor = 0xffff0000;
    private int unSelectedIndicatorColor = 0x88888888;
    private int titleBGColor = 0X55000000;
    private int titleColor = 0Xffffffff;

    private Shape indicatorShape = Shape.oval;
    private int selectedIndicatorHeight = 20;
    private int selectedIndecatorWidth = 20;
    private int unSelectedIndicatorHeight = 20;
    private int unSelectedIndecatorWidth = 20;

    private int autoPlayDuration = 4000;
    private int scrollDuration = 900;
//  指示器中點和點之間的距離
    private int indicatorSpace = 15;
//  指示器整體的設定
    private int indicatorMargin = 20;

    private static final int titlePadding = 20;

    private int defaultImage;

    private enum Shape {
        // 矩形 或 圓形的指示器
        rect, oval
    }

    private OnBannerItemClickListener mOnBannerItemClickListener;

    private Handler mHandler = new Handler(new Handler.Callback() {
        @Override
        public boolean handleMessage(Message msg) {
            if (msg.what == WHAT_AUTO_PLAY) {
                if (mViewPager != null) {
                    mViewPager.setCurrentItem(mViewPager.getCurrentItem() + 1, true);
                    mHandler.sendEmptyMessageDelayed(WHAT_AUTO_PLAY, autoPlayDuration);
                }
            }
            return false;
        }
    });

    public BannerLayout(Context context) {
        super(context);
        init(null, 0);
    }

    public BannerLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(attrs, 0);
    }

    public BannerLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(attrs, defStyleAttr);
    }

    private void init(AttributeSet attrs, int defStyleAttr) {

        TypedArray array = getContext().obtainStyledAttributes(attrs, R.styleable.BannerLayoutStyle, defStyleAttr, 0);
        selectedIndicatorColor = array.getColor(R.styleable.BannerLayoutStyle_selectedIndicatorColor, selectedIndicatorColor);
        unSelectedIndicatorColor = array.getColor(R.styleable.BannerLayoutStyle_unSelectedIndicatorColor, unSelectedIndicatorColor);

        titleBGColor = array.getColor(R.styleable.BannerLayoutStyle_titleBGColor, titleBGColor);
        titleColor = array.getColor(R.styleable.BannerLayoutStyle_titleColor, titleColor);

        int shape = array.getInt(R.styleable.BannerLayoutStyle_indicatorShape, Shape.oval.ordinal());

        for (Shape shape1 : Shape.values()) {
            if (shape1.ordinal() == shape) {
                indicatorShape = shape1;
                break;
            }
        }

        selectedIndecatorWidth = (int) array.getDimension(R.styleable.BannerLayoutStyle_selectedIndicatorWidth, selectedIndecatorWidth);
        selectedIndicatorHeight = (int) array.getDimension(R.styleable.BannerLayoutStyle_selectedIndicatorHeight, selectedIndicatorHeight);

        unSelectedIndecatorWidth = (int) array.getDimension(R.styleable.BannerLayoutStyle_unSelectedIndicatorWidth, unSelectedIndecatorWidth);
        unSelectedIndicatorHeight = (int) array.getDimension(R.styleable.BannerLayoutStyle_unSelectedIndicatorHeight, unSelectedIndicatorHeight);


        indicatorSpace = (int) array.getDimension(R.styleable.BannerLayoutStyle_indicatorSpace, indicatorSpace);
        indicatorMargin = (int) array.getDimension(R.styleable.BannerLayoutStyle_indicatorMargin, indicatorMargin);

        autoPlayDuration = array.getInt(R.styleable.BannerLayoutStyle_autoPlayDuration, autoPlayDuration);
        scrollDuration = array.getInt(R.styleable.BannerLayoutStyle_scrollDuration, scrollDuration);

        isAutoPlay = array.getBoolean(R.styleable.BannerLayoutStyle_isAutoPlay, isAutoPlay);

        defaultImage = array.getResourceId(R.styleable.BannerLayoutStyle_defaultImage, defaultImage);

        array.recycle();

        LayerDrawable unSelectedLayerDrawable;
        LayerDrawable selectedLayerDrawable;

        GradientDrawable unSelectedGradientDrawable = new GradientDrawable();
        GradientDrawable selectedGradientDtawbale = new GradientDrawable();

        switch (indicatorShape) {
            case rect:
                unSelectedGradientDrawable.setShape(GradientDrawable.RECTANGLE);
                selectedGradientDtawbale.setShape(GradientDrawable.RECTANGLE);
                break;
            case oval:
                unSelectedGradientDrawable.setShape(GradientDrawable.OVAL);
                selectedGradientDtawbale.setShape(GradientDrawable.OVAL);
                break;
        }

        unSelectedGradientDrawable.setColor(unSelectedIndicatorColor);
        unSelectedGradientDrawable.setSize(unSelectedIndecatorWidth, unSelectedIndicatorHeight);
        unSelectedLayerDrawable = new LayerDrawable(new Drawable[]{unSelectedGradientDrawable});
        unSelectedDrawable = unSelectedLayerDrawable;

        selectedGradientDtawbale.setColor(selectedIndicatorColor);
        selectedGradientDtawbale.setSize(selectedIndecatorWidth, selectedIndicatorHeight);
        selectedLayerDrawable = new LayerDrawable(new Drawable[]{selectedGradientDtawbale});

        selectedDrawable = selectedLayerDrawable;

    }

    /**
     * 新增本地圖片
     *
     * @param viewRes 圖片id集合
     * @param titles  標題集合,可空
     */
    public void setViewRes(List<Integer> viewRes, List<String> titles) {
        List<View> views = new ArrayList<>();
        itemCount = viewRes.size();
        if (titles != null && titles.size() != viewRes.size()) {
            throw new IllegalStateException("views.size() != titles.size()");
        }
        // 把數量拼湊到三個以上
        if (itemCount < 1) {
            throw new IllegalStateException("item count not equal zero");
        } else if (itemCount < 2) {
            views.add(getFrameLayoutView(viewRes.get(0), titles != null ? titles.get(0) : null, 0));
            views.add(getFrameLayoutView(viewRes.get(0), titles != null ? titles.get(0) : null, 0));
            views.add(getFrameLayoutView(viewRes.get(0), titles != null ? titles.get(0) : null, 0));

        } else if (itemCount < 3) {
            views.add(getFrameLayoutView(viewRes.get(0), titles != null ? titles.get(0) : null, 0));
            views.add(getFrameLayoutView(viewRes.get(1), titles != null ? titles.get(1) : null, 1));
            views.add(getFrameLayoutView(viewRes.get(0), titles != null ? titles.get(0) : null, 0));
            views.add(getFrameLayoutView(viewRes.get(1), titles != null ? titles.get(1) : null, 1));
        } else {
            for (int i = 0; i < viewRes.size(); i++) {
                views.add(getFrameLayoutView(viewRes.get(i), titles != null ? titles.get(i) : null, i));
            }
        }
        setViews(views);
    }

    //新增網路圖片路徑
    public void setViewUrls(Context context,List<String> urls, List<String> titles) {
        List<View> views = new ArrayList<>();
        itemCount = urls.size();
        if (titles != null && titles.size() != itemCount) {
            throw new IllegalStateException("views.size() != titles.size()");
        }

        //主要是解決當item為小於3個的時候滑動有問題,這裡將其拼湊成3個以上
        if (itemCount < 1) {//當item個數0
            throw new IllegalStateException("item count not equal zero");
        } else if (itemCount < 2) { //當item個數為1
            views.add(getFrameLayoutView(context,urls.get(0), titles != null ? titles.get(0) : null, 0));
            views.add(getFrameLayoutView(context,urls.get(0), titles != null ? titles.get(0) : null, 0));
            views.add(getFrameLayoutView(context,urls.get(0), titles != null ? titles.get(0) : null, 0));
        } else if (itemCount < 3) {//當item個數為2
            views.add(getFrameLayoutView(context,urls.get(0), titles != null ? titles.get(0) : null, 0));
            views.add(getFrameLayoutView(context,urls.get(1), titles != null ? titles.get(1) : null, 1));
            views.add(getFrameLayoutView(context,urls.get(0), titles != null ? titles.get(0) : null, 0));
            views.add(getFrameLayoutView(context,urls.get(1), titles != null ? titles.get(1) : null, 1));

        } else {
            for (int i = 0; i < urls.size(); i++) {
                views.add(getFrameLayoutView(context,urls.get(i), titles != null ? titles.get(i) : null, i));
            }
        }
        setViews(views);
    }

    private void setViews(final List<View> views) {

        mViewPager = new ViewPager(getContext());

        addView(mViewPager);
        setSliderTransformDuration(scrollDuration);
        //初始化indicatorContainer
        indicatorContainer = new LinearLayout(getContext());
        indicatorContainer.setGravity(Gravity.CENTER_VERTICAL);
        LayoutParams params = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
        // 設定 指示點的位置
        params.addRule(RelativeLayout.CENTER_HORIZONTAL);
        params.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);

        //設定margin
        params.setMargins(indicatorMargin, indicatorMargin, indicatorMargin, indicatorMargin);
        //新增指示器容器佈局到SliderLayout
        addView(indicatorContainer, params);

        //初始化指示器,並新增到指示器容器佈局
        for (int i = 0; i < itemCount; i++) {
            ImageView indicator = new ImageView(getContext());
            indicator.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
            indicator.setPadding(indicatorSpace, indicatorSpace, indicatorSpace, indicatorSpace);
            indicator.setImageDrawable(unSelectedDrawable);
            indicatorContainer.addView(indicator);
        }
        LoopPagerAdapter pagerAdapter = new LoopPagerAdapter(views);
        mViewPager.setAdapter(pagerAdapter);
        //設定當前item到Integer.MAX_VALUE中間的一個值,看起來像無論是往前滑還是往後滑都是ok的
        //如果不設定,使用者往左邊滑動的時候已經劃不動了
        int targetItemPosition = Integer.MAX_VALUE / 2 - Integer.MAX_VALUE / 2 % itemCount;
        mViewPager.setCurrentItem(targetItemPosition);
        switchIndicator(targetItemPosition % itemCount);
        mViewPager.addOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
            @Override
            public void onPageSelected(int position) {
                switchIndicator(position % itemCount);
            }
        });
        startAutoPlay();

    }

    /**
     * 開始自動輪播
     */
    public void startAutoPlay() {
        stopAutoPlay(); // 避免重複訊息
        if (isAutoPlay) {
            mHandler.sendEmptyMessageDelayed(WHAT_AUTO_PLAY, autoPlayDuration);
        }
    }

    @Override
    protected void onWindowVisibilityChanged(int visibility) {
        super.onWindowVisibilityChanged(visibility);
        if (visibility == VISIBLE) {
            startAutoPlay();
        } else {
            stopAutoPlay();
        }
    }


    /**
     * 停止自動輪播
     */
    public void stopAutoPlay() {
        if (isAutoPlay) {
            mHandler.removeMessages(WHAT_AUTO_PLAY);
        }
    }

    private void setSliderTransformDuration(int scrollDuration) {
        try {
            Field mScroller = ViewPager.class.getDeclaredField("mScroller");
            mScroller.setAccessible(true);
            FixedSpeedScroller fixedSpeedScroller = new FixedSpeedScroller(mViewPager.getContext(), null, scrollDuration);
            mScroller.set(mViewPager, fixedSpeedScroller);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                stopAutoPlay();
                break;
            case MotionEvent.ACTION_CANCEL:
            case MotionEvent.ACTION_UP:
                startAutoPlay();
                break;
        }
        return super.dispatchTouchEvent(ev);
    }

    /**
     * 切換指示器狀態
     *
     * @param currentPosition 當前位置
     */
    private void switchIndicator(int currentPosition) {
        for (int i = 0; i < indicatorContainer.getChildCount(); i++) {
            ((ImageView) indicatorContainer.getChildAt(i)).setImageDrawable(i == currentPosition ? selectedDrawable : unSelectedDrawable);
        }
    }


    public void setOnBannerItemClickListener(OnBannerItemClickListener onBannerItemClickListener) {
        this.mOnBannerItemClickListener = onBannerItemClickListener;
    }

    @NonNull
    private FrameLayout getFrameLayoutView(Context context, String url, String title, final int position) {
        FrameLayout frameLayout = new FrameLayout(getContext());
        FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
        layoutParams.gravity = Gravity.CENTER;
        ImageView imageView = new ImageView(getContext());
        frameLayout.addView(imageView);
        frameLayout.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                if (mOnBannerItemClickListener != null) {
                    mOnBannerItemClickListener.onItemClick(position);
                }
            }
        });
        imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
        if (defaultImage != 0){
            Glide.with(getContext()).load(url).placeholder(defaultImage).centerCrop().into(imageView);
        }else {
            Glide.with(getContext()).load(url).centerCrop().into(imageView);
        }
        if (!TextUtils.isEmpty(title)) {
            TextView textView = new TextView(getContext());
            textView.setText(title);
            textView.setTextColor(titleColor);
            textView.setPadding(titlePadding, titlePadding, titlePadding, titlePadding);
            textView.setBackgroundColor(titleBGColor);
            textView.getPaint().setFakeBoldText(true);
            layoutParams.gravity = Gravity.BOTTOM;
            frameLayout.addView(textView, layoutParams);
        }
        return frameLayout;
    }

    @NonNull
    private FrameLayout getFrameLayoutView(Integer res, String title, final int position) {
        FrameLayout frameLayout = new FrameLayout(getContext());
        FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
        layoutParams.gravity = Gravity.CENTER;
        ImageView imageView = new ImageView(getContext());
        frameLayout.addView(imageView);
        frameLayout.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                if (mOnBannerItemClickListener != null) {
                    mOnBannerItemClickListener.onItemClick(position);
                }
            }
        });
        imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
        Glide.with(getContext()).load(res).centerCrop().into(imageView);
        if (!TextUtils.isEmpty(title)) {
            TextView textView = new TextView(getContext());
            textView.setText(title);
            textView.setTextColor(titleColor);
            textView.setPadding(titlePadding, titlePadding, titlePadding, titlePadding);
            textView.setBackgroundColor(titleBGColor);
            textView.getPaint().setFakeBoldText(true);
            layoutParams.gravity = Gravity.BOTTOM;
            frameLayout.addView(textView, layoutParams);
        }
        return frameLayout;
    }

    public interface OnBannerItemClickListener {
        void onItemClick(int position);
    }

    public class LoopPagerAdapter extends PagerAdapter {
        private List<View> views;

        public LoopPagerAdapter(List<View> views) {
            this.views = views;
        }

        @Override
        public int getCount() {
            //Integer.MAX_VALUE = 2147483647
            return Integer.MAX_VALUE;
        }

        @Override
        public boolean isViewFromObject(View view, Object object) {
            return view == object;
        }

        @Override
        public Object instantiateItem(ViewGroup container, int position) {
            if (views.size() > 0) {
                //position % view.size()是指虛擬的position會在[0,view.size())之間迴圈
                View view = views.get(position % views.size());
                if (container.equals(view.getParent())) {
                    container.removeView(view);
                }
                container.addView(view);
                return view;
            }
            return null;
        }

        @Override
        public void destroyItem(ViewGroup container, int position, Object object) {
        }
    }

    public class FixedSpeedScroller extends Scroller {

        private int mDuration = 1000;

        public FixedSpeedScroller(Context context) {
            super(context);
        }

        public FixedSpeedScroller(Context context, Interpolator interpolator) {
            super(context, interpolator);
        }

        public FixedSpeedScroller(Context context, Interpolator interpolator, int duration) {
            this(context, interpolator);
            mDuration = duration;
        }

        @Override
        public void startScroll(int startX, int startY, int dx, int dy, int duration) {
            // Ignore received duration, use fixed one instead
            super.startScroll(startX, startY, dx, dy, mDuration);
        }

        @Override
        public void startScroll(int startX, int startY, int dx, int dy) {
            // Ignore received duration, use fixed one instead
            super.startScroll(startX, startY, dx, dy, mDuration);
        }
    }

}
4.attr.xml中
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <attr name="visible_tab_count" format="integer"/>
    <declare-styleable name="BannerLayoutStyle">
        <attr name="selectedIndicatorColor" format="color|reference"/>
        <attr name="unSelectedIndicatorColor" format="color|reference"/>
        <attr name="titleBGColor" format="color|reference"/>
        <attr name="titleColor" format="color|reference"/>


        <attr name="indicatorShape" format="enum">
            <enum name="rect" value="0"/>
            <enum name="oval" value="1"/>
        </attr>

        <attr name="selectedIndicatorHeight" format="dimension|reference"/>
        <attr name="selectedIndicatorWidth" format="dimension|reference"/>
        <attr name="unSelectedIndicatorHeight" format="dimension|reference"/>
        <attr name="unSelectedIndicatorWidth" format="dimension|reference"/>

        <attr name="indicatorSpace" format="dimension|reference"/>
        <attr name="indicatorMargin" format="dimension|reference"/>
        <attr name="autoPlayDuration" format="integer|reference"/>
        <attr name="scrollDuration" format="integer|reference"/>
        <attr name="isAutoPlay" format="boolean"/>
        <attr name="defaultImage" format="integer|reference"/>

    </declare-styleable>
</resources>

5.activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"

    android:layout_height="match_parent"
    android:orientation="vertical"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.twosx.app.bannerdemo.MainActivity">

    <com.example.zhh.mytestbanner.BannerLayout
        android:id="@+id/banner1"
        android:layout_width="match_parent"
        android:layout_height="180dp"
        app:defaultImage="@mipmap/ic_launcher"
        app:indicatorMargin="10dp"
        app:indicatorShape="oval"
        app:indicatorSpace="2dp"
        app:scrollDuration="1100"
        app:selectedIndicatorColor="?attr/colorPrimary"
        app:selectedIndicatorHeight="6dp"
        app:selectedIndicatorWidth="6dp"
        app:titleColor="#ff0000"
        app:unSelectedIndicatorColor="#99ffffff"
        app:unSelectedIndicatorHeight="20dp"
        app:unSelectedIndicatorWidth="20dp"/>

    <com.example.zhh.mytestbanner.BannerLayout
        android:id="@+id/banner2"
        android:layout_width="match_parent"
        android:layout_height="180dp"
        android:layout_marginTop="16dp"
        app:defaultImage="@mipmap/ic_launcher"
        app:scrollDuration="1000"
        app:autoPlayDuration="3000"
        app:unSelectedIndicatorHeight="10dp"
        app:unSelectedIndicatorWidth="10dp"
        app:selectedIndicatorHeight="10dp"
        app:selectedIndicatorWidth="10dp"
        app:unSelectedIndicatorColor="#99ffffff"
        app:selectedIndicatorColor="#000000"
        app:isAutoPlay="true"
        />

</LinearLayout>
6.MainActivity
package com.example.zhh.mytestbanner;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.text.Html;
import android.widget.TextView;
import android.widget.Toast;

import java.util.ArrayList;
import java.util.List;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        BannerLayout bannerLayout1 = (BannerLayout) findViewById(R.id.banner1);
        List<Integer> res = new ArrayList<>();
        res.add(R.drawable.lunbo1);
        res.add(R.drawable.lunbo2);
        res.add(R.drawable.lunbo3);
        List<String> titles = new ArrayList<>();
        titles.add("標題一");
        titles.add("標題二");
        titles.add("標題三");
        if (bannerLayout1 != null) {
            bannerLayout1.setViewRes(res, titles);
        }

        BannerLayout bannerLayout2 = (BannerLayout) findViewById(R.id.banner2);

        List<String> urls = new ArrayList<>();
        urls.add("http://www.5uplus.com/image/upload/app_mall_img/7de8813b-3e41-4854-9f5a-c54b044cb21c.jpg");
        urls.add("http://www.5uplus.com/image/upload/app_mall_img/58fd5357-ce41-491f-be2e-a3fe13e86d51.jpg");
        urls.add("http://www.5uplus.com/image/upload/app_mall_img/5991ccc6-c125-4506-a610-4fa40e14781f.jpg");
        urls.add("http://www.5uplus.com/image/upload/app_mall_img/abc67d59-65b8-4893-93ea-ff342a7fb992.jpg");

        if (bannerLayout2 != null) {
            bannerLayout2.setViewUrls(MainActivity.this ,urls, null);
            bannerLayout2.setOnBannerItemClickListener(new BannerLayout.OnBannerItemClickListener() {
                @Override
                public void onItemClick(int position) {
                    Toast.makeText(MainActivity.this, "position:" + position, Toast.LENGTH_SHORT).show();
                }
            });
        }


    }
}

原始碼下載:

轉載地址: