自定义组合控件-轮播图

# 自定义组合控件-轮播图

自定义控件系列课程在这里

Android开发自定义控件系列课程 (opens new window)

前面我们做了一个自定义组合控件,登录的界面。

开发日常-编写一个登录界面 (opens new window)

接下来,我们写一个轮播图的例子

20191111_221307.png

# 分析

轮播图可以由哪些控件组成呢?

轮播的内容可以用ViewPager来显示吧

文字可以用TextView来显示吧

圆点可以用View来显示吧

# 约定

  • 一般自己写的View放在一个views的包下,所以创建一个views的目录吧
  • 命名,公司名/项目名为前缀,后面为功能名。当然啦,你也可以用你对象的名字或者孩子的名字来命名。
  • 类顶部的注释要有详细的使用方法

# 代码编写

其实组合控件,按步骤来就好

# 继承自LinearLayout或RelatviewLayout

public class SobLooperView extends LinearLayout {
    public SobLooperView(Context context) {
        //确保统一入口
        this(context,null);
    }

    public SobLooperView(Context context,@Nullable AttributeSet attrs) {
        //确保统一入口
        this(context,attrs,0);
    }

    public SobLooperView(Context context,@Nullable AttributeSet attrs,int defStyleAttr) {
        super(context,attrs,defStyleAttr);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

那我不继承自LinerLayout或者RelativeLayout可以吗?

当然可以,继承自一个ViewGroup只是作为容器来方其他要组合在一起的子控件。

# 编写要组合的控件内容

根据前面的分析,我们写成这个样子:layout_looper_view.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="https://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <!--view pager,我用的是androidx的,不是以前的v4包-->
    <androidx.viewpager.widget.ViewPager
        android:id="@+id/content_pager"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

    <!--标题控件-->
    <TextView
        android:id="@+id/content_title"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:layout_alignParentBottom="true"
        android:layout_marginLeft="20dp"
        android:layout_marginRight="20dp"
        android:layout_marginBottom="20dp"
        android:text="这是标题内容..." />

    <!--用来放圆点-->
    <LinearLayout
        android:id="@+id/content_point_container"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_centerHorizontal="true"
        android:layout_marginBottom="10dp"
        android:orientation="horizontal" />

</RelativeLayout>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

# 把需要的组合的控件创建或者加入进来

把布局layout_looper_view.xml载入到我们前面写好的容器里

请详细看看注释内容,这就给大家解释了什么时候,inflate最后一个参数attach填ture还是false了。

public class SobLooperView extends LinearLayout {
    public SobLooperView(Context context) {
        //确保统一入口
        this(context,null);
    }

    public SobLooperView(Context context,@Nullable AttributeSet attrs) {
        //确保统一入口
        this(context,attrs,0);
    }

    public SobLooperView(Context context,@Nullable AttributeSet attrs,int defStyleAttr) {
        super(context,attrs,defStyleAttr);
        //ViewPager
        //TextView
        //点容器,点需要动态地创建,因为点的个数跟内容个数有关系,同学们现在明白第三个参数为什么填写true了吧。
        //填写true的话,就是自动填充到前面的viewGroup里
        LayoutInflater.from(context).inflate(R.layout.layout_looper_view,this,true);
        //等价于如下:
        //View content = LayoutInflater.from(context).inflate(R.layout.layout_looper_view,this,false);
        //addView(content);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 找到子控件

我们把view都绑到当前的view里了,所以可以用this.findViewById()

public class SobLooperView extends LinearLayout {

    private ViewPager mViewPager;
    private TextView mTitleView;
    private LinearLayout mPointCotainer;

    public SobLooperView(Context context) {
        //确保统一入口
        this(context,null);
    }

    public SobLooperView(Context context,@Nullable AttributeSet attrs) {
        //确保统一入口
        this(context,attrs,0);
    }

    public SobLooperView(Context context,@Nullable AttributeSet attrs,int defStyleAttr) {
        super(context,attrs,defStyleAttr);
        //ViewPager
        //TextView
        //点容器,点需要动态地创建,因为点的个数跟内容个数有关系,同学们现在明白第三个参数为什么填写true了吧。
        //填写true的话,就是自动填充到前面的viewGroup里
        LayoutInflater.from(context).inflate(R.layout.layout_looper_view,this,true);
        //等价于如下:
        //View content = LayoutInflater.from(context).inflate(R.layout.layout_looper_view,this,false);
        //addView(content);
        initView();
    }

    /**
     * 找到子控件
     */
    private void initView() {
        mViewPager = this.findViewById(R.id.content_pager);
        mTitleView = this.findViewById(R.id.content_title);
        mPointCotainer = this.findViewById(R.id.content_point_container);
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

# 提供设置数据的方法和回调

我们这里面,可以让外部把数据给进来,包括图片的地址/路径,标题。但是,如果是内部去实现这些功能,那扩展性就不好了。

那你是网络地址,还是本地图片呢?所以呢,我们让外面去给一个适配器进来,设置给pager就好了,使用的人爱怎么设置就怎么设置。如果作为设计的人,你还得考虑后面的无限轮播。无限轮播我们

那还有,标题呢?我们可以定好接口,把当前的position传出去呀,至于要设置什么内容,我才不管呢。

所以就有了:

   //提供方法给外部设置适配器进来,这个适配器怎么我们有规定,所以使用了一个抽象类来描述。
    //而TitleBindListener用来获取标题,我们要标题的时候调用即可。
    public void setData(InnerPageAdapter innerPageAdapter,TitleBindListener listener) {
        mViewPager.setAdapter(innerPageAdapter);
        this.mTitleBindListener = listener;
    }

    public interface TitleBindListener {
        String getTitle(int position);
    }

    public static abstract class InnerPageAdapter extends PagerAdapter {

        public abstract int getDataSize();

        @Override
        public int getCount() {
            //因为要无限轮播嘛,所以我们就给一个IntegerMaxValue
            return Integer.MAX_VALUE;
        }

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

        @NonNull
        @Override
        public Object instantiateItem(@NonNull ViewGroup container,int position) {
            //载入view,至于显示什么view,不用管,由外面给进来。只要对position进行一个转换
            int itemPosition = position % getDataSize();
            View itemView = getItemView(container,itemPosition);
            container.addView(itemView);
            return itemView;
        }

        //至于要什么view,我不管,由外面给我。
        protected abstract View getItemView(ViewGroup container,int itemPosition);

        @Override
        public void destroyItem(@NonNull ViewGroup container,int position,@NonNull Object object) {
            container.removeView((View) object);
        }
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44

到这里,pager的内容就有了,但是呢,标题和圆点没整呢,怎么整呢?

# 设置pager的滑动切换监听

20191116_145841.png

/**
     * 设置相关事件
     */
    private void initEvent() {
        mViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {

            /**
             *
             * @param position position有两个情况,position positionOffset 为0时,就是当前的Position
             *                 如果有滑动,position则会是下一个准备看到的position
             *
             * @param positionOffset 位置偏移量,取值为0到1,[0,1)
             * @param positionOffsetPixels 位置偏移量,这个是像素界别的
             */
            @Override
            public void onPageScrolled(int position,float positionOffset,int positionOffsetPixels) {
                //滑动时的回调
            }

            @Override
            public void onPageSelected(int position) {
                //滑动以后停下来的回调,position指所停在的位置
                //这个时候我们去获取标题
                if(mTitleBindListener != null) {
                    String title = mTitleBindListener.getTitle(position);
                    mTitleView.setText(title);
                }
            }

            @Override
            public void onPageScrollStateChanged(int state) {
                //滑动状态的改变,有停止的,滑动中的.
                //ViewPager#SCROLL_STATE_IDLE
                //ViewPager#SCROLL_STATE_DRAGGING
                //ViewPager#SCROLL_STATE_SETTLING
            }
        });
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

这样子,当页面选中的时候,就会去调用外部设置进来的接口实现方法获得标题,设置到控件上。

# 动态添加圆点

为什么要动态添加呢?因为我们不知道有多少个内容呀,所以等到使用者把数据设置进来以后,我们就根据数据动态地创建圆点,添加到容器里。

所以就有了以下的代码。

private void updateIndicator() {
        if(mInnerAdapter != null) {
            //先删除
            mPointContainer.removeAllViews();
            int indicatorSize = mInnerAdapter.getDataSize();
            for(int i = 0; i < indicatorSize; i++) {
                View view = new View(getContext());
                if((mViewPager.getCurrentItem() % mInnerAdapter.getDataSize() == i)) {
                    view.setBackgroundColor(Color.parseColor("#ff0000"));
                } else {
                    view.setBackgroundColor(Color.parseColor("#ffffff"));
                }
                LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(SizeUtils.dip2px(getContext(),5),SizeUtils.dip2px(getContext(),5));
                layoutParams.setMargins(SizeUtils.dip2px(getContext(),5),0,SizeUtils.dip2px(getContext(),5),0);
                view.setLayoutParams(layoutParams);
                //添加到容器里
                mPointContainer.addView(view);
            }
        }
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

这样子也不好表达,这样子吧,我把代码贴出来,然后去看视频好了。

# 自定义属性

自定义属性的话看视频吧

# 代码

SizeUtils.java工具类

package com.sunofbeaches.looperdemo.views;

import android.content.Context;

public class SizeUtils {

    public static int dip2px(Context context,float dpValue) {
        float scale = context.getResources().getDisplayMetrics().density;
        return (int) (dpValue * scale + 0.5f);
    }

}
1
2
3
4
5
6
7
8
9
10
11
12

SobViewPager.java

这个是覆写了ViewPager,做自动轮播处理

package com.sunofbeaches.looperdemo.views;

import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.viewpager.widget.ViewPager;

public class SobViewPager extends ViewPager {


    private Handler mHandler;

    public SobViewPager(@NonNull Context context) {
        this(context,null);
        setPageTransformer(true,new PageTransformer() {
            @Override
            public void transformPage(@NonNull View page,float position) {

            }
        });
    }

    public SobViewPager(@NonNull Context context,@Nullable AttributeSet attrs) {
        super(context,attrs);
        mHandler = new Handler(Looper.getMainLooper());
        setOnTouchListener(new OnTouchListener() {
            @Override
            public boolean onTouch(View v,MotionEvent event) {
                //不处理事件
                int action = event.getAction();
                switch(action) {
                    case MotionEvent.ACTION_DOWN:
                        pauseLooper();
                        break;
                    case MotionEvent.ACTION_CANCEL:
                    case MotionEvent.ACTION_UP:
                        resumeLooper();
                        break;
                }
                return false;
            }
        });
    }

    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
        this.resumeLooper();
    }

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        this.pauseLooper();
    }

    private void resumeLooper() {
        //继续轮播
        mHandler.postDelayed(mTask,1000);
    }

    private Runnable mTask = new Runnable() {
        @Override
        public void run() {
            int currentItem = getCurrentItem();
            currentItem++;
            setCurrentItem(currentItem);
            mHandler.postDelayed(this,1000);
        }
    };

    private void pauseLooper() {
        //暂停轮播
        mHandler.removeCallbacks(mTask);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82

这是组合控件的类,SobLooperView.java

package com.sunofbeaches.looperdemo.views;

import android.content.Context;
import android.database.DataSetObserver;
import android.graphics.Color;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.TextView;

import com.sunofbeaches.looperdemo.R;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.viewpager.widget.PagerAdapter;
import androidx.viewpager.widget.ViewPager;

public class SobLooperView extends LinearLayout {

    private ViewPager mViewPager;
    private TextView mTitleView;
    private LinearLayout mPointContainer;
    private TitleBindListener mTitleBindListener = null;
    private InnerPageAdapter mInnerAdapter = null;

    public SobLooperView(Context context) {
        //确保统一入口
        this(context,null);
    }

    public SobLooperView(Context context,@Nullable AttributeSet attrs) {
        //确保统一入口
        this(context,attrs,0);
    }

    public SobLooperView(Context context,@Nullable AttributeSet attrs,int defStyleAttr) {
        super(context,attrs,defStyleAttr);
        //ViewPager
        //TextView
        //点容器,点需要动态地创建,因为点的个数跟内容个数有关系,同学们现在明白第三个参数为什么填写true了吧。
        //填写true的话,就是自动填充到前面的viewGroup里
        LayoutInflater.from(context).inflate(R.layout.layout_looper_view,this,true);
        //等价于如下:
        //View content = LayoutInflater.from(context).inflate(R.layout.layout_looper_view,this,false);
        //addView(content);
        initView();
        initEvent();
    }

    /**
     * 设置相关事件
     */
    private void initEvent() {
        mViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {

            /**
             *
             * @param position position有两个情况,position positionOffset 为0时,就是当前的Position
             *                 如果有滑动,position则会是下一个准备看到的position
             *
             * @param positionOffset 位置偏移量,取值为0到1,[0,1)
             * @param positionOffsetPixels 位置偏移量,这个是像素界别的
             */
            @Override
            public void onPageScrolled(int position,float positionOffset,int positionOffsetPixels) {
                //滑动时的回调
            }

            @Override
            public void onPageSelected(int position) {
                //滑动以后停下来的回调,position指所停在的位置
                //这个时候我们去获取标题
                if(mTitleBindListener != null && mInnerAdapter != null) {
                    String title = mTitleBindListener.getTitle(position % mInnerAdapter.getDataSize());
                    mTitleView.setText(title);
                }
                updateIndicator();
            }

            @Override
            public void onPageScrollStateChanged(int state) {
                //滑动状态的改变,有停止的,滑动中的.
                //ViewPager#SCROLL_STATE_IDLE
                //ViewPager#SCROLL_STATE_DRAGGING
                //ViewPager#SCROLL_STATE_SETTLING
            }
        });
    }

    /**
     * 找到子控件
     */
    private void initView() {
        mViewPager = this.findViewById(R.id.content_pager);
        mViewPager.setPageMargin(SizeUtils.dip2px(getContext(),20));
        mViewPager.setOffscreenPageLimit(3);
        mTitleView = this.findViewById(R.id.content_title);
        mPointContainer = this.findViewById(R.id.content_point_container);
    }


    //提供方法给外部设置适配器进来,这个适配器怎么我们有规定,所以使用了一个抽象类来描述。
    //而TitleBindListener用来获取标题,我们要标题的时候调用即可。
    public void setData(InnerPageAdapter innerPageAdapter,TitleBindListener listener) {
        mViewPager.setAdapter(innerPageAdapter);
        mViewPager.setCurrentItem(Integer.MAX_VALUE / 2 + 1);
        this.mInnerAdapter = innerPageAdapter;
        this.mTitleBindListener = listener;
        if(mTitleBindListener != null) {
            String title = mTitleBindListener.getTitle(0);
            mTitleView.setText(title);
        }
        //创建圆点
        updateIndicator();
        innerPageAdapter.registerDataSetObserver(new DataSetObserver() {
            @Override
            public void onChanged() {
                updateIndicator();
            }
        });
    }

    private void updateIndicator() {
        if(mInnerAdapter != null) {
            //先删除
            mPointContainer.removeAllViews();
            int indicatorSize = mInnerAdapter.getDataSize();
            for(int i = 0; i < indicatorSize; i++) {
                View view = new View(getContext());
                if((mViewPager.getCurrentItem() % mInnerAdapter.getDataSize() == i)) {
                    view.setBackgroundColor(Color.parseColor("#ff0000"));
                } else {
                    view.setBackgroundColor(Color.parseColor("#ffffff"));
                }
                LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(SizeUtils.dip2px(getContext(),5),SizeUtils.dip2px(getContext(),5));
                layoutParams.setMargins(SizeUtils.dip2px(getContext(),5),0,SizeUtils.dip2px(getContext(),5),0);
                view.setLayoutParams(layoutParams);
                //添加到容器里
                mPointContainer.addView(view);
            }
        }
    }

    public interface TitleBindListener {
        String getTitle(int position);
    }

    public static abstract class InnerPageAdapter extends PagerAdapter {

        public abstract int getDataSize();

        @Override
        public int getCount() {
            //因为要无限轮播嘛,所以我们就给一个IntegerMaxValue
            return Integer.MAX_VALUE;
        }

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

        @NonNull
        @Override
        public Object instantiateItem(@NonNull ViewGroup container,int position) {
            //载入view,至于显示什么view,不用管,由外面给进来。只要对position进行一个转换
            int itemPosition = position % getDataSize();
            View itemView = getItemView(container,itemPosition);
            container.addView(itemView);
            return itemView;
        }

        //至于要什么view,我不管,由外面给我。
        protected abstract View getItemView(ViewGroup container,int itemPosition);

        @Override
        public void destroyItem(@NonNull ViewGroup container,int position,@NonNull Object object) {
            container.removeView((View) object);
        }
    }

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185

相关布局:layout_looper_view.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="https://schemas.android.com/apk/res/android"
    android:clipChildren="false"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <!--view pager,我用的是androidx的,不是以前的v4包-->
    <com.sunofbeaches.looperdemo.views.SobViewPager
        android:id="@+id/content_pager"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:clipChildren="false"
        android:layout_marginLeft="40dp"
        android:layout_gravity="center"
        android:layout_marginRight="40dp" />

    <!--标题控件-->
    <TextView
        android:id="@+id/content_title"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="#99ffffff"
        android:paddingLeft="20dp"
        android:paddingTop="2dp"
        android:paddingRight="20dp"
        android:paddingBottom="2dp"
        android:text="这是标题内容..."
        android:textAlignment="center"
        android:textSize="12sp" />

    <!--用来放圆点-->
    <LinearLayout
        android:id="@+id/content_point_container"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_centerHorizontal="true"
        android:layout_marginBottom="10dp"
        android:orientation="horizontal" />

</RelativeLayout>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41

# 使用写好的控件

布局上引用

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="https://schemas.android.com/apk/res/android"
    xmlns:tools="https://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">


    <com.sunofbeaches.looperdemo.views.SobLooperView
        android:layout_width="match_parent"
        android:id="@+id/sob_looper"
        android:layout_height="120dp" />

</RelativeLayout>
1
2
3
4
5
6
7
8
9
10
11
12
13
14

设置数据即可

package com.sunofbeaches.looperdemo;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;

import com.sunofbeaches.looperdemo.domain.LooperItem;
import com.sunofbeaches.looperdemo.views.SobLooperView;

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

public class MainActivity extends AppCompatActivity {

    private List<LooperItem> mData;
    private SobLooperView mLooperView;

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

    private void initView() {
        mLooperView = this.findViewById(R.id.sob_looper);
        mLooperView.setData(new SobLooperView.InnerPageAdapter() {
            @Override
            public int getDataSize() {
                return mData.size();
            }

            @Override
            protected View getItemView(ViewGroup container,int itemPosition) {
                ImageView imageView = new ImageView(container.getContext());
                imageView.setScaleType(ImageView.ScaleType.FIT_XY);
                //设置图片
                imageView.setImageResource(mData.get(itemPosition).getImgRsId());
                ViewGroup.LayoutParams layoutParams = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.MATCH_PARENT);
                imageView.setLayoutParams(layoutParams);
                return imageView;
            }
        },new SobLooperView.TitleBindListener() {
            @Override
            public String getTitle(int position) {
                return mData.get(position).getTitle();
            }
        });
    }

    private void initTestData() {
        mData = new ArrayList<>();
        mData.add(new LooperItem("图片1的标题",R.mipmap.pic1));
        mData.add(new LooperItem("图片2的标题",R.mipmap.pic2));
        mData.add(new LooperItem("图片3的标题",R.mipmap.pic3));
        mData.add(new LooperItem("图片4的标题",R.mipmap.pic4));
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63

# 效果

viewGroup.gif

# 像素转dp工具类

public class SizeUtils {

    public static int dip2px(Context context,float dpValue) {
        float scale = context.getResources().getDisplayMetrics().density;
        return (int) (dpValue * scale + 0.5f);
    }

}
1
2
3
4
5
6
7
8
上次更新: 2021/10/22, 23:03:57