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

心得感悟

臨近假期終于又有時(shí)間好好寫(xiě)簡(jiǎn)書(shū)了,花了很久時(shí)間查閱資料,其實(shí)每次查完資料,我都感覺(jué)懂了很多,又還有好多不懂。想用三種方式做出一樣的效果,可試了很多次發(fā)現(xiàn)自己的知識(shí)儲(chǔ)備實(shí)在是嚴(yán)重不足無(wú)法實(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ǔ)充說(shuō)明
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

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

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è)開(kāi)始才需要間距
            if (i > 0){
                params.leftMargin = dpToPixel(10);
            }
            // 添加到容器中
            container.addView(dotView,params);
        }
    }

    // 由于手機(jī)屏幕密度不同,常需要寫(xiě)一些方法解決控件的顯示問(wèn)題
    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){
           //還原原來(lái)的
            //找到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,重寫(xiě)構(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);

        // 保存外部傳過(guò)來(lái)的數(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,并重寫(xiě)構(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里面自定義的屬性取出來(lái)
         */
        if (attrs != null){
            /**
             * 從一個(gè)資源文件里面將自定義的所有屬性取出來(lái)
             * 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)前頁(yè)數(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;

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

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

    /**
     * 寬度拉伸的動(dòng)畫(huà)
     */
    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,編寫(xiě)自定義屬性。


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

<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)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 說(shuō)到輪播圖,想必大家都不陌生。常見(jiàn)的APP都會(huì)有一個(gè)圖片輪播的區(qū)域。之前使用過(guò)輪播圖,最近項(xiàng)目又一次用到了,就把原...
    IAM四十二閱讀 7,106評(píng)論 7 39
  • 【Android 動(dòng)畫(huà)】 動(dòng)畫(huà)分類補(bǔ)間動(dòng)畫(huà)(Tween動(dòng)畫(huà))幀動(dòng)畫(huà)(Frame 動(dòng)畫(huà))屬性動(dòng)畫(huà)(Property ...
    Rtia閱讀 6,392評(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,409評(píng)論 0 16
  • 一、簡(jiǎn)歷準(zhǔn)備 1、個(gè)人技能 (1)自定義控件、UI設(shè)計(jì)、常用動(dòng)畫(huà)特效 自定義控件 ①為什么要自定義控件? Andr...
    lucas777閱讀 5,389評(píng)論 2 54
  • 生活對(duì)對(duì)碰 31.在英國(guó),中超不是球隊(duì),而指中國(guó)超市。在華人居住多的地區(qū),中超較多,購(gòu)物很方便。但在英國(guó)人居住區(qū),...
    沒(méi)有天賦也不努力閱讀 339評(píng)論 0 0

友情鏈接更多精彩內(nèi)容