【Android開發(fā)】圓點(diǎn)滑動(dòng)條——自定義組合方式

心得感悟

臨近假期終于又有時(shí)間好好寫簡(jiǎn)書了,花了很久時(shí)間查閱資料,其實(shí)每次查完資料苫纤,我都感覺懂了很多,又還有好多不懂纲缓。想用三種方式做出一樣的效果卷拘,可試了很多次發(fā)現(xiàn)自己的知識(shí)儲(chǔ)備實(shí)在是嚴(yán)重不足無法實(shí)現(xiàn),唉祝高,今天也要加油鴨栗弟。


內(nèi)容簡(jiǎn)概

一、傳統(tǒng)方式(不用自定義控件)
二工闺、組合?式
三乍赫、組合?式+自定義屬性
四、效果圖

具體內(nèi)容

一陆蟆、傳統(tǒng)方式(不用自定義控件)

1. 用到的基本知識(shí)點(diǎn)
方法名 作用
getChildAt(int position) 獲取當(dāng)前點(diǎn)擊或者選中的View
LayoutParams 動(dòng)態(tài)控制子view的擺放位置
Gravity 控制元素在該控件里的顯示位置
density 獲取屏幕密度
??????????????補(bǔ)充說明
ViewGroup.LayoutParams.MATCH_PARENT 雷厂,意思為寬度和父view相同
ViewGroup.LayoutParams.WRAP_CONTENT,意思為自適應(yīng)
Gravity方法的使用與注意點(diǎn)
屏幕密度(Density)和分辨率概念詳解
2. activity_main.xml

首先叠殷,我們?cè)谠撐募刑砑泳€性布局改鲫。

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <!--添加線性布局-->
    <LinearLayout
        android:id="@+id/ll_container"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:layout_centerInParent="true">
    </LinearLayout>

</RelativeLayout>
3. dot_gray_shape.xml和dot_red_shape.xml

Android\app\res\drawable中新建兩個(gè)drawable resource file,并取名如標(biāo)題(當(dāng)然你也可以自己另外命名)

<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="oval">
    <!--normal狀態(tài)下-->
    <solid android:color="#666666"/>
    <size android:width="20dp" android:height="20dp"/>

</shape>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="oval">
    <!--selected狀態(tài)下-->
    <solid android:color="#ff0000"/>
    <size android:width="30dp" android:height="30dp"/>

</shape>
4. MainActivity.java

然后通過代碼添加圓點(diǎn)控件溪猿。用代碼添加控件的好處在于簡(jiǎn)便钩杰,這個(gè)例子中,我們需要添加5個(gè)圓點(diǎn)诊县,如果在xml中配置讲弄,會(huì)有許多重復(fù)語句;再者依痊,xml中往往是配置靜態(tài)的避除、不怎么需要變化的東西怎披,這個(gè)例子中的圓點(diǎn)需要在手指翻閱時(shí)做一點(diǎn)動(dòng)畫。

public class MainActivity extends AppCompatActivity {
    private int numberOfPages = 5;
    private int currentPage = 0;
    LinearLayout container;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // 獲取xml配置中的線性布局容器
        container = findViewById(R.id.ll_container);

        // 在容器中添加內(nèi)容-View
        for (int i = 0; i < 5; i++) {
            // 創(chuàng)建視圖控件
            ImageView dotView = new ImageView(this);
            // 配置顯示樣子
            if (i == 0){
                // 第一個(gè)顯示紅點(diǎn)
                dotView.setBackgroundResource(R.drawable.dot_red_shape);
            }else {
                dotView.setBackgroundResource(R.drawable.dot_gray_shape);
            }
            // 給控件添加左間距
            LinearLayout.LayoutParams params = new LinearLayout.LayoutParams
                    (ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
            // 設(shè)置垂直居中
            params.gravity = Gravity.CENTER_VERTICAL;
            // 第二個(gè)開始才需要間距
            if (i > 0){
                params.leftMargin = dpToPixel(10);
            }
            // 添加到容器中
            container.addView(dotView,params);
        }
    }

    // 由于手機(jī)屏幕密度不同瓶摆,常需要寫一些方法解決控件的顯示問題
    private int dpToPixel(int dp){
        // 獲取屏幕密度
        float density = getResources().getDisplayMetrics().density;
        return (int) (density * dp);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (event.getAction() == MotionEvent.ACTION_DOWN){
           //還原原來的
            //找到page對(duì)應(yīng)的控件
            ImageView dotView = (ImageView) container.getChildAt(currentPage);
            dotView.setBackgroundResource(R.drawable.dot_gray_shape);
            // 切換指示器
           if (currentPage < numberOfPages-1){
               currentPage++;
           }else {
               currentPage = 0;
           }
           // 找到當(dāng)前指示的控件
            ImageView current = (ImageView) container.getChildAt(currentPage);
           current.setBackgroundResource(R.drawable.dot_red_shape);
        }
        return true;
    }
}

二凉逛、組合?式

1. PageController.java

首先需要為自定義控件創(chuàng)建一個(gè)類。在Android\(model名)\java\com.example.group文件夾中新建一個(gè)類群井,并使其繼承于LinearLayout状飞,重寫構(gòu)造方法。


2. dot_gray_shape.xml和dot_red_shape.xml

這里同樣需要設(shè)置圓點(diǎn)的樣式书斜。

<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="oval">
    <!--normal狀態(tài)下-->
    <solid android:color="#666666"/>
    <size android:width="20dp" android:height="20dp"/>
</shape>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="oval">
    <!--selected狀態(tài)下-->
    <solid android:color="#ff0000"/>
    <size android:width="30dp" android:height="30dp"/>
</shape>
3. activity_main.xml

xml文件中只需將ConstraintLayout改為RelativeLayout诬辈,并加上ID。

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity"
    android:id="@+id/root">
</RelativeLayout>
4. PageController

自定義控件一般都要實(shí)現(xiàn)下圖中藍(lán)色選中的方法荐吉,否則自定義控件不起效果焙糟。



在類中設(shè)置我們需要的方法。

public class PageController extends LinearLayout {
    private int numberOfPages;  // 記錄多少個(gè)
    private int padding; // 間距
    public int normalResourse; // 正常狀態(tài)的資源
    public int selectedResourse; // 選中狀態(tài)的資源

    // 當(dāng)使用Java代碼創(chuàng)建控件時(shí) 用這個(gè)構(gòu)造方法
    public PageController(Context context,int normalRes,int selectedRes,int padding) {
        super(context,null);

        // 保存外部傳過來的數(shù)據(jù)
        normalResourse = normalRes;
        selectedResourse = selectedRes;
        this.padding = padding;
    }
    // xml里面配置
    public PageController(Context context, @Nullable AttributeSet attrs) {
        this(context,attrs,0);
    }
    // xml里面還配置了樣式的
    public PageController(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        setOrientation(LinearLayout.HORIZONTAL);
    }

    /**
     * setter/getter方法
     */
    public int getNumberOfPages() {
        return numberOfPages;
    }

    public void setNumberOfPages(int numberOfPages) {
        this.numberOfPages = numberOfPages;

        // 依次創(chuàng)建每個(gè)指示點(diǎn)
        for (int i = 0; i < numberOfPages; i++) {
            // 創(chuàng)建控件
            ImageView dotView = new ImageView(getContext());
            // 創(chuàng)建布局參數(shù)
            LayoutParams params = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
            // 設(shè)置資源
            if (i == 0){
                dotView.setImageResource(selectedResourse);
            }else {
                dotView.setImageResource(normalResourse);
                // 設(shè)置間距
                params.leftMargin = padding;
            }
            // 垂直居中
            params.gravity = Gravity.CENTER_VERTICAL;
            // 添加控件
            addView(dotView,params);
        }
    }

    /**
     * padding的setter/getter方法
     */
    public int getPadding() {
        return padding;
    }

    public void setPadding(int padding) {
        this.padding = padding;
    }

    /**
     * 定義一個(gè)接口 在接口里面定義常量表示狀態(tài)
     */
    public interface DotState{
        int NORMAL = 0;
        int SELECTED = 1;
    }
}

對(duì)比傳統(tǒng)方式样屠,組合方式創(chuàng)建自定義控件可以使xml文件和Java文件代碼更加簡(jiǎn)潔穿撮。

三、組合?式+自定義屬性

1. PageController.java

首先痪欲,和方式二相同創(chuàng)建一個(gè)類繼承于LinearLayout悦穿,并重寫構(gòu)造方法。

public class PageController extends LinearLayout {
    private int numberOfPages;
    public int resourceID; // 不同狀態(tài)下顯示的形狀和顏色
    public int padding; // 間距
    public int currentPage; // 記錄當(dāng)前指示是第幾個(gè)
    private PageChangeListener mPageChangeListener; // 回調(diào)對(duì)象

    // 代碼創(chuàng)建
    public PageController(Context context) {
        this(context,null);
    }

    // xml配置
    public PageController(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs,0);
    }

    public PageController(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        // 設(shè)置對(duì)齊方式
        setOrientation(LinearLayout.HORIZONTAL);
        setGravity(Gravity.CENTER);
        /**
         * 將xml里面自定義的屬性取出來
         */
        if (attrs != null){
            /**
             * 從一個(gè)資源文件里面將自定義的所有屬性取出來
             * 1. Attributes xml配置里的所有屬性
             * 2. 自定義的屬性文件 R.styleable.name
             */

            TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.PageController);
            /**
             * 1. 自定義屬性名 自定義屬性名_屬性名
             * 2. 默認(rèn)值
             */
            padding = typedArray.getInt(R.styleable.PageController_mPadding,0);
            resourceID = typedArray.getResourceId(R.styleable.PageController_resourceID,0);
            int page = typedArray.getInt(R.styleable.PageController_numberOfPage,0);

            // 顯示
            setNumberOfPages(page);
        }
    }
    /**
     * numberOfPages setter/getter方法
     */
    public int getNumberOfPages() {
        return numberOfPages;
    }

    public void setNumberOfPages(int numberOfPages) {
        this.numberOfPages = numberOfPages;

        // 創(chuàng)建點(diǎn)
        for (int i = 0; i < numberOfPages; i++) {
            // 創(chuàng)建控件
            ImageView dotView = new ImageView(getContext());
            // 設(shè)置顯示的內(nèi)容
            dotView.setBackgroundResource(resourceID);
            // 設(shè)置約束
            LayoutParams params = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
            params.gravity = Gravity.CENTER_VERTICAL;
            if (i > 0) {
                params.leftMargin = padding;
            }else {
                // 默認(rèn)選擇第一個(gè)點(diǎn)
                dotView.setEnabled(false);
            }

            // 添加到容器中
            addView(dotView,params);
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (event.getAction() == MotionEvent.ACTION_DOWN){
            // 取出當(dāng)前頁數(shù)
            int current = currentPage;
            if (event.getX() > getWidth()*0.5){
                // 右邊
                if (current == numberOfPages - 1){
                    current = 0;
                }else {
                    current++;
                }
            }else {
                // 左邊
                if (current == 0){
                    current = numberOfPages - 1;
                }else {
                    current--;
                }
            }
            setCurrentPage(current);
        }
        return true;
    }
    /**
     * currentPage
     */
    public int getCurrentPage() {
        return currentPage;
    }

    public void setCurrentPage(int currentPage) {
        // 將上一次的還原為默認(rèn)狀態(tài)
        ImageView old = (ImageView) getChildAt(this.currentPage);
        old.setEnabled(true);
        // 將當(dāng)前選中的設(shè)置為選中狀態(tài)
        ImageView current = (ImageView) getChildAt(currentPage);
        current.setEnabled(false);
        this.currentPage = currentPage;

        // 將頁數(shù)改變的事件回調(diào)給監(jiān)聽者
        if (mPageChangeListener != null){
            mPageChangeListener.pageDidChange(currentPage);
        }
        //開啟動(dòng)畫
        showAnimation(current);
    }
    /**
     * 定義接口監(jiān)聽指示器改變的事件
     */
    public interface PageChangeListener{
        void pageDidChange(int currentPage);
    }

    /**
     * mPageChangeListener
     * 設(shè)置監(jiān)聽對(duì)象
     */
    public void addPageChangeListener(PageChangeListener listener){
        this.mPageChangeListener = listener;
    }

    /**
     * 寬度拉伸的動(dòng)畫
     */
    public void showAnimation(ImageView item){
        ObjectAnimator scale = ObjectAnimator.ofFloat(item,"scaleX",1,1.5f,1);
        scale.setDuration(400);
        scale.start();
    }

    /**
     * padding
     */
    public int getPadding() {
        return padding;
    }
    public void setPadding(int padding) {
        this.padding = padding;
    }
}
2. page_control_attr.xml

在customattr/res/values中新建一個(gè)Value resource file业踢,編寫自定義屬性咧党。


創(chuàng)建自定義屬性說明:
①在Value中新建一個(gè)Value resource file
②使用declare-styleable關(guān)鍵字修飾
③name為自己定義的類名
④format添加屬性和對(duì)應(yīng)的值的類型
⑤可以添加多種類型,類型間用“ | ”隔開

<resources>
    <!--聲明在哪個(gè)控件上添加屬性-->
    <declare-styleable name="PageController">
        <attr name="mPadding" format="integer"/>
        <attr name="resourceID" format="reference|color"/>
        <attr name="numberOfPage" format="integer"/>
    </declare-styleable>
</resources>
3 dot_shape.xml

在drawable文件下新建一個(gè)資源文件陨亡,設(shè)置圓點(diǎn)形狀。

<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <!--enables為false 選中-->
    <item android:state_enabled="false">
        <shape android:shape="oval">
            <size android:height="50dp" android:width="50dp"></size>
            <solid android:color="#ff0000"></solid>
        </shape>
    </item>

    <!--enables為true 默認(rèn)-->
    <item>
        <shape android:shape="oval">
            <size android:height="50dp" android:width="50dp"></size>
            <solid android:color="#666666"></solid>
        </shape>
    </item>
</selector>
4. activity_main.xml

添加自定義布局屬性深员。

<RelativeLayout 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"
    tools:context=".MainActivity"
    android:id="@+id/root">

    <com.example.customattr.PageController
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:mPadding="20"
        app:resourceID="@drawable/dot_shape"
        app:numberOfPage="5"/>
</RelativeLayout>
5. MainActivity.java

很明顯代碼又簡(jiǎn)潔了許多负蠕。

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}

四、效果圖

  • 組合方式


  • 繼承方式(靜態(tài))


  • 自繪方式


最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末倦畅,一起剝皮案震驚了整個(gè)濱河市遮糖,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌叠赐,老刑警劉巖欲账,帶你破解...
    沈念sama閱讀 218,682評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異芭概,居然都是意外死亡赛不,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門罢洲,熙熙樓的掌柜王于貴愁眉苦臉地迎上來踢故,“玉大人,你說我怎么就攤上這事〉罱希” “怎么了耸峭?”我有些...
    開封第一講書人閱讀 165,083評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長淋纲。 經(jīng)常有香客問我劳闹,道長,這世上最難降的妖魔是什么洽瞬? 我笑而不...
    開封第一講書人閱讀 58,763評(píng)論 1 295
  • 正文 為了忘掉前任本涕,我火速辦了婚禮,結(jié)果婚禮上片任,老公的妹妹穿的比我還像新娘偏友。我一直安慰自己,他們只是感情好对供,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,785評(píng)論 6 392
  • 文/花漫 我一把揭開白布位他。 她就那樣靜靜地躺著,像睡著了一般产场。 火紅的嫁衣襯著肌膚如雪鹅髓。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,624評(píng)論 1 305
  • 那天京景,我揣著相機(jī)與錄音窿冯,去河邊找鬼。 笑死确徙,一個(gè)胖子當(dāng)著我的面吹牛醒串,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播鄙皇,決...
    沈念sama閱讀 40,358評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼芜赌,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了伴逸?” 一聲冷哼從身側(cè)響起缠沈,我...
    開封第一講書人閱讀 39,261評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎错蝴,沒想到半個(gè)月后洲愤,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,722評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡顷锰,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年柬赐,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片官紫。...
    茶點(diǎn)故事閱讀 40,030評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡躺率,死狀恐怖玛界,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情悼吱,我是刑警寧澤慎框,帶...
    沈念sama閱讀 35,737評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站后添,受9級(jí)特大地震影響笨枯,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜遇西,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,360評(píng)論 3 330
  • 文/蒙蒙 一馅精、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧粱檀,春花似錦洲敢、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至渗常,卻和暖如春壮不,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背皱碘。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評(píng)論 1 270
  • 我被黑心中介騙來泰國打工询一, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人癌椿。 一個(gè)月前我還...
    沈念sama閱讀 48,237評(píng)論 3 371
  • 正文 我出身青樓健蕊,卻偏偏與公主長得像,于是被迫代替她去往敵國和親踢俄。 傳聞我的和親對(duì)象是個(gè)殘疾皇子绊诲,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,976評(píng)論 2 355

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

  • 說到輪播圖,想必大家都不陌生褪贵。常見的APP都會(huì)有一個(gè)圖片輪播的區(qū)域。之前使用過輪播圖抗俄,最近項(xiàng)目又一次用到了脆丁,就把原...
    IAM四十二閱讀 6,712評(píng)論 7 39
  • 【Android 動(dòng)畫】 動(dòng)畫分類補(bǔ)間動(dòng)畫(Tween動(dòng)畫)幀動(dòng)畫(Frame 動(dòng)畫)屬性動(dòng)畫(Property ...
    Rtia閱讀 6,164評(píng)論 1 38
  • 前言 最近做項(xiàng)目碰到一個(gè)這樣的一個(gè)需求:需要一個(gè)環(huán)形的進(jìn)度條表示一個(gè)下載請(qǐng)求的進(jìn)度加載。同時(shí)要以各種不同的圖標(biāo)展現(xiàn)...
    chengww閱讀 3,299評(píng)論 0 16
  • 一动雹、簡(jiǎn)歷準(zhǔn)備 1槽卫、個(gè)人技能 (1)自定義控件、UI設(shè)計(jì)胰蝠、常用動(dòng)畫特效 自定義控件 ①為什么要自定義控件歼培? Andr...
    lucas777閱讀 5,208評(píng)論 2 54
  • 生活對(duì)對(duì)碰 31.在英國震蒋,中超不是球隊(duì),而指中國超市躲庄。在華人居住多的地區(qū)查剖,中超較多,購物很方便噪窘。但在英國人居住區(qū)笋庄,...
    沒有天賦也不努力閱讀 294評(píng)論 0 0