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实例。

  • 相关阅读:
    安装docker
    docker安装完成后测试feisky/nginx:latest 出现问题(Unable to find image 'feisky/nginx:latest' locally)
    负载均衡详细介绍
    docker(一)
    AppCrawer使用(2)
    AppCrawer使用入门(转自https://www.cnblogs.com/alwayswyy/p/7576964.html,感谢原作者)
    获取包名和活动名
    (十八)Collection集合
    (十七)面向对象之内部类
    (十五)final关键字
  • 原文地址:https://www.cnblogs.com/sunshine-anycall/p/4929541.html
Copyright © 2011-2022 走看看