zoukankan      html  css  js  c++  java
  • Android Studio自定义组合控件

    在Android的开发中,为了能够服用代码,会把有一定共有特点的控件组合在一起定义成一个自定义组合控件。 
    本文就详细讲述这一过程。虽然这样的View的组合有一个粒度的问题。粒度太大了无法复用,粒度太小了又 
    达不到很好的复用的效果。不过,这些不在本文的讨论范围,需要读者自己去开发的实践中体会。

    实例项目就选择一个登录注册的组件,这组件包括用户名、密码的文本输入框,还有登录和注册的按钮。这里 
    主要是为了讲解的需要,在选择服用代码的力度上可以不用参考。 
    默认的当一个新的项目创建以后就会生成一个Activity和与之相应的一个布局文件。这些已经足够使用。 
    这里假设你默认生成的Activity名称为MainActivity,布局文件为activity_main.xml。

    首先,创建一个以LinearLayout为基类的View。这个View的名字就叫做LoginView。

    /**
     * Created by Bruce on 31/10/15.
     */
    public class LoginView extends LinearLayout {
    
        private Context _context;
    
        public LoginView(Context context) {
            this(context, null);
        }
    
        public LoginView(Context context, AttributeSet attrs) {
            super(context, attrs);
            _context = context;
    
            //...
        }
    }

    代码中包含了一个Context的成员,因为我们在后面需要用到。 

    之后,创建这个View需要的布局文件:

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:padding="10dp">
    
        <EditText android:id="@+id/userName"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="User name" />
    
        <EditText android:id="@+id/password"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="Password" />
    
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal">
    
            <Button android:id="@+id/loginButton"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:text="Login" />
    
            <Button android:id="@+id/signupButton"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:text="Sign Up" />
        </LinearLayout>
    
    
    </LinearLayout>

    按照前文所述,我们要做的是一个登录的界面包含用户名、密码和登录、注册按钮,一共四个子组件。在布局登录、 
    注册按钮的是时候,需要在横向布局。所以,单独使用了一个新的LinearLayout,设定这个layout的方向(orientation) 
    为横向(horizental)。两个按钮的宽度都设定为0dp,因为有layout_weight。给layout_weight分别设定了1 
    之后,这两个按钮将平分他们所在的Linearlayout的宽度。

    把这个控件使用在MainActivity中。按照惯例在activity_main中添加控件。只不过这次需要使用的全名称 
    的限定。就是需要把这个View的完整包路径全部写出来:

    <?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:fitsSystemWindows="true"
        tools:context=".MainActivity">
    
        <com.example.home.draganddraw.LoginView
            android:id="@+id/loginView"
            android:layout_width="match_parent"
            android:layout_height="match_parent">
    
        </com.example.home.draganddraw.LoginView>
    
        <!-- 其他省略 -->
    
    </LinearLayout>
    

    com.example.home.draganddraw.LoginView就是这个View的全名称。同时我们给这个LoginView指定 
    了id为loginView。在MainActivity的java文件中可以取到这个View:

    LoginView loginView = (LoginView)findViewById(R.id.loginView);

    这个时候可以run起来这个项目。but,这样又有什么卵用呢?点个按钮也没什么反应。是的,我们需要给这个组合控件 
    添加代码。我们需要从布局文件中解析出这些单独的控件,EditText和Button。就像是在Activity中经常做的 
    那样:

    View view = LayoutInflater.from(context).inflate(R.layout.view_login, this, true);
    EditText userName = (EditText) view.findViewById(R.id.userName);
    EditText password = (EditText) view.findViewById(R.id.password);
    Button loginButton = (Button) view.findViewById(R.id.loginButton);
    Button signupButton = (Button) view.findViewById(R.id.signupButton);

    给按钮设置Click Listener。首先让按钮能有反应。那么需要一个OnClickListener。我们这里只有 
    两个按钮,所以只要在类的级别设定出监听器就可以:

    /**
     * Created by Bruce on 31/10/15.
     */
    public class LoginView extends LinearLayout implements View.OnClickListener {
    
        //...
    
        public LoginView(Context context, AttributeSet attrs) {
            super(context, attrs);
            _context = context;
    
            View view = LayoutInflater.from(context).inflate(R.layout.view_login, this, true);
            EditText userName = (EditText) view.findViewById(R.id.userName);
            EditText password = (EditText) view.findViewById(R.id.password);
            Button loginButton = (Button) view.findViewById(R.id.loginButton);
            Button signupButton = (Button) view.findViewById(R.id.signupButton);
    
            loginButton.setOnClickListener(this);
            signupButton.setOnClickListener(this);
        }
    
        @Override
        public void onClick(View v) {
            if (v.getId() == R.id.loginButton) {
                Toast.makeText(MainActivity.this, "Login", Toast.LENGTH_LONG).show();
            } else if (v.getId() == R.id.signupButton) {
                Toast.makeText(MainActivity.this, "Register", Toast.LENGTH_LONG).show();
            }
        }
    
        //...
    }
    

    用户在点击了按钮之后就会弹出一个Toast来显示你点击的是哪个按钮,“Login”和“Register”。好了,终于有 
    反映了,但是还是不够的。用户对这个View的操作需要交给Activity做特定的处理,而不是我们直接就把这些 
    功能在View里全部处理。这样,怎么能打到复用代码的目的呢?所以,我们需要把按钮的点击事件交给MainActivity 
    来处理。

    在iOS里,就是在控件中定义一个Delegate(java的interface),然后在Controller(Activity)中实现 
    并在组合控件中调用这个实现。一般来说,上面代码中public class LoginView extends LinearLayout implements View.OnClickListener 
    和方法public void onClick(View v)然后signupButton.setOnClickListener(this);就是这么一个意思。 
    只不过我们只能看到是怎么用的,但是也可以猜到是怎么定义这个interface的。以上可以总结为:

    1. 控件中定义接口。
    2. 在Activity的实现。
    3. 在控件中使用activity的实现。

    定义接口:

        /**
        * Created by Bruce on 31/10/15.
        */
        public class LoginView extends LinearLayout implements View.OnClickListener {
            private Context _context;
    
            //...
    
            @Override
            public void onClick(View v) {
                //...
            }
    
            public void setOnLoginViewClickListener(OnLoginViewClickListener loginViewClickListener) {
                //...
            }
    
            public interface OnLoginViewClickListener {
                void loginViewButtonClicked(View v);
            }
        }

    这里我们定义了接口public interface OnLoginViewClickListener还有这么一个方法void loginViewButtonClicked(View v);。 
    然后定义了这个接口的setter,是啊,activity的实现我们要怎么使用呢?就是通过这个setter给组合控件赋值 
    过来,然后使用的嘛。

    下面在activity中实现这个接口(这个在java里比在ObjC里简单多了好吗):

    public class MainActivity extends AppCompatActivity {
    
        private LoginView _loginView;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            _loginView = (LoginView)findViewById(R.id.loginView);
            _loginView.setOnLoginViewClickListener(new LoginView.OnLoginViewClickListener() {
                @Override
                public void loginViewButtonClicked(View v) {
                    if (v.getId() == R.id.loginButton) {
                        Toast.makeText(MainActivity.this, "Login", Toast.LENGTH_LONG).show();
                    } else if (v.getId() == R.id.signupButton) {
                        Toast.makeText(MainActivity.this, "Register", Toast.LENGTH_LONG).show();
                    }
                }
            });
        }
    }

    实现这个接口的时候,直接在LoginView的实现上把接口new出来一个实例就可以。这个东西在ObjC里墨迹的半死。 
    现在还有人说java实现个回调太麻烦,这个有OC复杂吗?

    要在LoginView中使用这个接口的实现就更加简单了。直接上代码:

    /**
     * Created by Bruce on 31/10/15.
     */
    public class LoginView extends LinearLayout implements View.OnClickListener {
    
        private Context _context;
        private OnLoginViewClickListener _onLoginViewClickListener;
    
        //...
    
        @Override
        public void onClick(View v) {
            if (_onLoginViewClickListener != null) {
                _onLoginViewClickListener.loginViewButtonClicked(v);
            }
        }
    
        public void setOnLoginViewClickListener(OnLoginViewClickListener loginViewClickListener) {
            _onLoginViewClickListener = loginViewClickListener;
        }
    
        public interface OnLoginViewClickListener {
            void loginViewButtonClicked(View v);
        }
    }
    

    在LoginView中定义接口的成员private OnLoginViewClickListener _onLoginViewClickListener;。 
    在setter中把这个接口的实现赋值给这个LoginView的成员变量,完事儿:

    public void setOnLoginViewClickListener(OnLoginViewClickListener loginViewClickListener) {
        _onLoginViewClickListener = loginViewClickListener;
    }

    这个时候再次运行项目,点击按钮之后出现的Toast就是我们在activity里的实现了。

    到这里,这个组合控件就已经有一定的使用价值了。定义了一个接口,这个接口的实现也在activity里定义了出来。 
    把这个控件放在任何一个需要登录的actvity里都可以把用户点击按钮之后的操作给activity实现,想怎么实现 
    都可以。

    但是,我们现在需要把这个控件的用户名和密码的输入框的hint也放在外面去实现。这个需求并不复杂。既然接口的 
    实现可以在setter里实现,那么hint当然也是可以的。没错,但是这并不符合android的实现要求–什么都在 
    xml文件中定义。这样也更加便于管理。同事也方便添加,直接在LoginView的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:fitsSystemWindows="true"
        tools:context=".MainActivity">
    
        <!--<include layout="@layout/content_main" />-->
        <com.example.home.draganddraw.LoginView
            android:id="@+id/loginView"
            app:UserNameHint="yo bro"
            app:PasswordHint="hey wsp"
            android:layout_width="match_parent"
            android:layout_height="match_parent">
    
        </com.example.home.draganddraw.LoginView>
    
    </LinearLayout>

    属性app:UserNameHint="yo bro"app:PasswordHint="hey wsp"就是我们自定义的属性。 
    直接像系统内置的属性使用一样就可以。这样比隐藏在代码中的setter方便了很多。

    添加,在values文件夹下添加attrs.xml文件。然后在文件中添加:

    <?xml version="1.0" encoding="utf-8"?>
    <resources>
        <declare-styleable name="LoginView">
            <attr name="UserNameHint" format="string"/>
            <attr name="PasswordHint" format="string"/>
        </declare-styleable>
    </resources>

    我们要给LoginView两个属性,一个是UserNameHint一个是PasswordHint。后面的format是这个属性 
    的格式,这里都是string。

    定义了属性,写在xml文件里还是不管用的。需要我们在自定义的view里添加代码才行。代码:

    public LoginView(Context context, AttributeSet attrs) {
        super(context, attrs);
            _context = context;
    
            TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.LoginView, defStyle, 0);
            CharSequence userNameHint = typedArray.getText(R.styleable.LoginView_UserNameHint);
            CharSequence passwordHint = typedArray.getText(R.styleable.LoginView_PasswordHint);
    
            View view = LayoutInflater.from(context).inflate(R.layout.view_login, this, true);
            EditText userName = (EditText) view.findViewById(R.id.userName);
            EditText password = (EditText) view.findViewById(R.id.password);
            Button loginButton = (Button) view.findViewById(R.id.loginButton);
            Button signupButton = (Button) view.findViewById(R.id.signupButton);
    
            userName.setHint(userNameHint);
            password.setHint(passwordHint);
            loginButton.setOnClickListener(this);
            signupButton.setOnClickListener(this);
    }

    TypedArray来完成解析和取值的工作。之后给EditText分别设定hint。

    再次运行项目就可以看到你设定的hint出现了。但是,有一个错误。是的有错误。TypedArray需要回收 
    所以取到所需要的值以后,typedArray.recycle();回收TypedArray实例。

  • 相关阅读:
    627. Swap Salary
    176. Second Highest Salary
    596. Classes More Than 5 Students
    183. Customers Who Never Order
    181. Employees Earning More Than Their Managers
    182. Duplicate Emails
    175. Combine Two Tables
    620. Not Boring Movies
    595. Big Countries
    HDU 6034 Balala Power! (贪心+坑题)
  • 原文地址:https://www.cnblogs.com/sunshine-anycall/p/4929541.html
Copyright © 2011-2022 走看看