引言

作为Android App,给人第一印象的就是用户界面(UI),简洁友好的UI,自然会给用户优秀的体验,自然很容易就得到用户的认可和赞许,这样App才变得真正的有价值。所以作为开发App的第一步,UI尤为重要,构建UI有很多种方式:xml静态布局java动态代码HTML构建(借助WebView)和第三方开源框架等。

一、构成UI的基本元素——View和ViewGroup概述

在Android中绝大部分的UI组件都是存放在android.widget包及其子包、android.view包及其子包,其中所有的UI视图组件都是继承自View类,其实View有点类似Swing编程的JPanel,代表着一个空白的矩形区域。View类还有一个重要的子类ViewGroup,所以它也具有View的特性,但它主要用来充当View的容器,将其中的View视作自己的孩子,对它的子View进行管理,当然它的孩子也可以是ViewGroup类型。但是ViewGroup并不难单独使用往往是作为其他的组件的容器。值得注意的是Android中的View与我们以前理解的“视图”不同。在Android中,View比视图具有更广的含义,它包含了用户交互和显示,更像Windows操作系统中的window。ViewGroup(根节点)和它的孩子们(View和ViewGroup)以树形结构形成了一个层次结构,View类有接受和处理消息的功能,android系统所产生的消息会在这些ViewGroup和 View之间传递。
这里写图片描述

由上图可见,作为容器的ViewGroup可以包含作为叶子节点的View,也可以包含作为更低层次的子ViewGroup,而子ViewGroup又可以包含下一层的叶子节点的View和ViewGroup。事实上,这种灵活的View层次结构可以形成非常复杂的UI布局,我们也可以据此设计并开发非常精致的UI界面。

二、View和ViewGroup简介

一般来说,开发Android应用程序的UI时我们都不是直接使用View和ViewGroup,而是使用他们各种的派生类。

  • View的直接子类:AnalogClock,ImageView,KeyboardView,
    ProgressBar,SurfaceView,TextView,ViewGroup,ViewStub

  • View的间接子类:AbsListView,AbsSeekBar, AbsSpinner, AbsoluteLayout,
    AdapterView,AdapterViewAnimator,
    AdapterViewFlipper, AppWidgetHostView,
    AutoCompleteTextView,Button,CalendarView, CheckBox, CheckedTextView,
    Chronometer, CompoundButton

  • ViewGroup的直接子类:AbsoluteLayout,AdapterView,FragmentBreadCrumbs,FrameLayout,LinearLayout,RelativeLayout,SlidingDrawer

  • ViewGroup的间接子类:AbsListView,AbsSpinner, AdapterViewAnimator,
    AdapterViewFlipper, AppWidgetHostView, CalendarView, DatePicker,
    DialerFilter, ExpandableListView, Gallery,
    GestureOverlayView,GridView,HorizontalScrollView,
    ImageSwitcher,ListView

以上这些所有基类、派生类都是Android framework层集成的标准系统类,开发者在应用开发中可直接引用SDK中这些系统类及其API。当然在很多时候,直接使用这些系统类并不能满足应用开发的需要,我们还得自己去开发自定义控件来满足自己的项目需求(这是后话了,以后再总结)。

三、View的常用xml属性及方法说明

在Android中组件的几乎所有属性都提供了两种方式来控制其行为,所以我们既可以在xml布局中直接静态赋值,也可以在代码中通过对应的方法进行动态控制,关系如下:

xml属性对应的方法说明
android:alphasetAlpha(float)设置组件的透明度(0——1)
android:backgroundsetBackgroundResource(int)设置背景
android:clickablesetClickable(boolean)设置是否可以接收点击事件并触发
android:duplicateParentState设置为true的时候可以直接从他直接的父组件中获取其状态(focused, pressed, etc.)
android:fadeScrollbarssetScrollbarFadingEnabled(boolean)但不使用滚动条时是否淡出显示滚动条
android:fadingEdgeLengthsetVerticalFadingEdgeLength()设置淡出边界的长度
android:fitsSystemWindowssetFitsSystemWindows(boolean)设置布局适应系统,比如说状态栏
android:focusablesetFocusable(boolean)是否可获取焦点
android:idsetId(int)设置Id
android:keepScreenOnsetKeepScreenOn(boolean)设置该组件是否会强制手机屏幕一直打开
android:layerTypesetLayerType(int,Paint)设置硬件加速
android:layoutDirectionsetLayoutDirection(int)设置布局从左向右,或从右向左,etc
android:longClickablesetLongClickable(boolean)设置是否可以接收长点击事件并触发
android:minHeight/minWidthsetMinimumHeight(int)/setMinimumWidth设置最小高(宽)度
android:nextFocusDownsetNextFocusDownId(int)设置焦点在该组件上且点击向下\左\右\上 键时获得焦点的组件Id
android:onClick点击时触发的方法
android:paddingsetPadding(int,int,int,int)设置内边距
android:rotationsetRotation(float)设置旋转角度
android:scaleX/YsetScaleX/Y(float)水平/垂直方向的缩放比
android:scrollX/Y初始化后组件的水平/垂直偏移
android:scrollbarSizesetScrollBarSize(int)设置水平滚动条的高度和垂直滚动条的宽度
android:scrollbarStylesetScrollBarStyle(int)设置滚动条的风格:insideOverlay、insideInset、outSideOverlay、outSideInset
android:scrollbarThumbHorizontal设置滚动条的Drawable对象
android:scrollbars设置是否显示滚动条或显示什么滚动条
android:soundEffectsEnabledsetSoundEffectsEnabled(boolean)设置组件按下后是否使用音效
android:tag和Id类似也可通过View.findViewWithTag()获取
android:textAlignmentsetTextAlignment(int)定义文字的对齐方式
android:transformPivotX/YsetPivotX/Y(float)设置旋转时的中心坐标X/Y
android:translationX/Y/ZsetTranslationX/Y/Z(float)设置位移
android:visibilitysetVisibility(int)设置是否可见
android:elevationsetElevation(float)设置该组件“浮”起来的高度,来呈现3D效果,android5.0 Material Design新增的属性

四、ViewGroup的重要内部类及常用布局、属性、方法

ViewGroup是一个特殊的View类,它继承于android.view.View。它的功能就是装载和管理下一层的View对象和ViewGroup对象。而ViewGroup是布局管理器(layout)及View容器的基类。而且ViewGroup中,还定义了2个内部类:ViewGroup.LayoutParamsViewGroup.MarginLayoutParams。这两个内部类定义了一个显示对象的位置、大小等属性,View还可以通过LayoutParams中的这些属性值来管理控制布局

1、ViewGroup.LayoutParams和ViewGroup.MarginLayoutParams

ViewGroup.MarginLayoutParams继承自ViewGroup.LayoutParams

xml属性相关方法说明
android:layout_height指定该子组件的布局高度
android:layout_width指定该子组件的布局宽度
ViewGroup.LayoutParams的构造方法
ViewGroup.LayoutParams(Context c, AttributeSet attrs)
ViewGroup.LayoutParams(int width, int height)
ViewGroup.LayoutParams(ViewGroup.LayoutParams source)

2、Anroid五种布局

  • FrameLayout:最简单的一个布局对象。它里面只显示一个显示对象。所有的显示对象都将会固定在屏幕的左上角,不能指定位置。但允许有多个显
    示对象,但后一个将会直接在前 一个之上进行覆盖显示,把前一个部份或全部挡住(除非后一个是透明的)。
  • LinearLayout:以单一方向对其中的显示对象进行排列显示,如以垂直排列显示,则布局管理器中将只有一列;如以水平排列显示,则布局管理器中将只有一行。同时,它还可以对个别的显示对象设置显示比例。

  • TableLayout:以拥有任意行列的表格对显示对象进行布局,每个显示对象被分配到各自的单元格之中,但单元格的边框线不可见。

  • RelativeLayout:允许通过指定显示对象相对于其它显示对象或父级对象的相对位置来布局。如一个按钮可以放于另一个按钮的左边,或者可以放在布局管理器的中央。

  • AbsoluteLayout:允许以坐标的方式,指定显示对象的具体位置,左上角的坐标为(0, 0),向下及向右,坐标值变大。这种布局管理器由于显示对象的位置定死了,所以在不同的设备上,有可能会出现最终的显示效果不一致,基本不用。

五、构建UI布局

1、通过xml静态布局构建

xml布局最常用,如果可以的话优先考虑xml静态布局,官方也十分推荐使用这种方式,因为他减弱了代码和视图的耦合。

  • 先定义xml布局文件
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout  <!--ViewGroup或子类作为根节点-->
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" android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".MainActivity"
    android:orientation="vertical">
<!--    <EditText
        android:id="@+id/id_content_edt"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="25sp"
        android:textColor="@color/colorAccent"
        android:hint="即将在Fragment中访问的目标"/>-->

       <!--引用自定义的View-->
       <com.crazymo.costompopwindow
           android:id=""
           ...
           >

       <com.crazymo.costompopwindow/>
    <FrameLayout
        android:id="@+id/id_content_layout"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"/>

</LinearLayout>
  • 然后再在Activity里的onCreate方法里调用setContentView(layoutId)设置即可

2、通过代码动态管理

  • 通过布局构造方法创建一种布局对象

  • 把布局设置到Activity里,相当于是把布局对象添加到Activity里

  • 再设置布局的相关属性

  • 根据业务需求设置View对象及位置

  • 再把View对象add至布局中

public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        LinearLayout layout=new LinearLayout(this);
        super.setContentView(layout);
        layout.setOrientation(LinearLayout.VERTICAL);
        TextView show=new TextView(this);
        show.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
        layout.addView(show);

    }
}

又如可以在Activity中动态添加和删除控件

/*1 取到那个Layout*/
ViewGroup viewGroup = (ViewGroup)findViewById(R.id.layout);
/*2添加时,先创建对象,然后添加*/
ImageView newImageView = new ImageView(
              Animation2Activity.this);
newImageView.setImageResource(R.drawable.an);
viewGroup.addView(newImageView,
              new LayoutParams(
                  LayoutParams.FILL_PARENT,
                  LayoutParams.WRAP_CONTENT));
/*3 删除时,直接删除。*/
viewGroup.removeView(imageView);

3、xml和代码混合布局

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent"
    >
    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Handler的应用——简易图片切换器"/>
    <ImageView
        android:id="@+id/id_show_img"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        />
</LinearLayout>
public class PicBrowserActivity extends Activity {
    private ImageView mImg;
    private int[] imgs=new int[]{
            R.mipmap.ic_blue_launcher,
            R.mipmap.ic_green_launcher,
            R.mipmap.ic_red_launcher,
            R.mipmap.pool_balls_05,
            R.mipmap.ic_toy,
            R.mipmap.ic_launcher
    };
    private int curIndex=0;//当前图片的id
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_handler);
        mImg= (ImageView) findViewById(R.id.id_show_img);
        final Handler handler=new Handler(){
            @Override
            public void handleMessage(Message msg) {
                if(msg.what==0x123){
                    mImg.setImageResource(imgs[curIndex++]);
                    if(curIndex>=5){
                        curIndex=5;
                    }
                }
            }
        };

        new Timer().schedule(new TimerTask() {
            @Override
            public void run() {
                Message msg=new Message();
                msg.what=0x123;
                handler.sendMessage(msg);
            }
        },0,1000);
    }
}

4、HTML代码构建

其实这一种严格来说应该不算是全新的方式,主要原理就是借助了WebView来实现javascript和java代码的互相交互,所以我们还可以通过HTML/HTML5的方式来构建自己的UI。

  • 首先,定义WebView,既然我们要借助WebView,肯定要先在xml布局文件中定义(当然也可以在代码中去构建)

布局很简单我就只定义了一个WebView

<?xml version="1.0" encoding="utf-8"?>
<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" android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".MainActivity">

    <WebView android:id="@+id/id_table_webview" android:layout_width="match_parent"
        android:layout_height="match_parent" />
</RelativeLayout>
  • 然后编写我们的html文件(也可以在代码中通过字符串的形式拼接成)
<!DOCTYPE html>
<head>
    <meta http-equiv="Content-Type" context="text/html;charset=UTF-8">
    <title>HTML UI FOR Andoid</title>
    <script type="text/javascript">
/**此方法是提供给java代码调用的,和其他的js方法没有什么不同,就像我们在web开发下用js动态生成table一样**/
function show(datas){
var jsonobjs=eval(datas);
var table=document.getElementById("table");
for(var i=0;i<jsonobjs.length;i++){
    var tr=table.insertRow(table.rows.length);
    var td=tr.insertCell(0);
    var td2=tr.insertCell(1);
    td2.align="right";
    var td3=tr.insertCell(2);
    td3.align="center";
    td.innerHTML=jsonobjs[i].id;
    td2.innerHTML=jsonobjs[i].name;
    td3.innerHTML=jsonobjs[i].phone;
}
}
</script>
</head>
<!--此employee为我们自己定义的js 对象,对应我们从Activity传递过来的js
自定义对象-->
<body onload="javascript:employee.showcontacts()">
<table border="1" width="100%" id="table" cellspacing="1">
    <tr>
        <td width="35%">姓名</td>
        <td width="30%">工号</td>
        <td width="35%">电话</td>
    </tr>
</table>
<a href="javascript:window.location.reload()">刷新</a>
</body>

单独的HTML界面效果是这样的
这里写图片描述

  • 再定义其他业务逻辑,比如说本例是获取表格数据的,自然还需要去实现获取数据的逻辑(JavaBean的Contacts实现就不贴出来了)
public class ContactsService {
    /**
     * 模拟获取信息
     * @return
     */
    public List<Contacts> getContactsImf(){
        List<Contacts> contacts=new ArrayList<Contacts>();
        contacts.add(new Contacts(2009,"CrazyMO",9389281));
        contacts.add(new Contacts(2010,"Jim",5641021));
        contacts.add(new Contacts(2011,"Winds",897512));
        contacts.add(new Contacts(2012,"Jack",4524323));
        return contacts;
    }
}
  • 最后我们的重头戏就是在清单中申请访问网络的权限和实现在WebView中实现java和javascript代码的交互,其实在这里有很多细节都需要注意我都在代码中一一注明,都是使用webView的相关知识点,这里不便扩展,否则跑偏啦。
package com.crazymo.htmlui;

import android.annotation.SuppressLint;
import android.app.Activity;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.webkit.JavascriptInterface;
import android.webkit.WebSettings;
import android.webkit.WebView;

import com.crazymo.entity.Contacts;
import com.crazymo.service.ContactsService;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.util.List;

public class MainActivity extends Activity {
    private WebView mWebview;
    private ContactsService mContactsService;
    private final String url = "file:///android_asset/index.html";

    @SuppressLint("JavascriptInterface")
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mWebview = (WebView) findViewById(R.id.id_table_webview);//获取WebVIew
        mWebview.getSettings().setLayoutAlgorithm(WebSettings.LayoutAlgorithm.SINGLE_COLUMN);///一系列的初始化设置
        mWebview.getSettings().setAllowFileAccess(true);// 设置允许访问文件数据
        mWebview.getSettings().setSupportZoom(true);//支持放大网页功能
        mWebview.getSettings().setBuiltInZoomControls(true);//支持缩小网页功能
        mWebview.getSettings().setJavaScriptEnabled(true);
        mWebview.addJavascriptInterface(new JSObject(), "employee");//前面对象,后面js中的调用名(我们可以看成这个JSObject类的实例是employee,用于给javascript里调用
        mWebview.setWebViewClient(new MyWebViewCient());//设置打开i时不用系统浏览器。使用本地WebView打开
        mWebview.loadUrl(url);
        mContactsService = new ContactsService();
    }

    private final class JSObject {
        /**
         * html中通过自定义的js 对象调用
         * 高能预警:If you've set your targetSdkVersion to 17 or higher, you must add the @JavascriptInterface
         即在一切需要在JS中调用的对象方法前加上@JavascriptInterface, 在api 17 即 Android 4.2.2 之后
         */
        @JavascriptInterface
        public void showcontacts() {

            List<Contacts> contactses = mContactsService.getContactsImf();
            JSONArray jsonArray = new JSONArray();
            try {
                for (Contacts contact : contactses) {
                    JSONObject jsonObject = new JSONObject();
                    jsonObject.put("id", contact.getId());
                    jsonObject.put("name", contact.getName());
                    jsonObject.put("phone", contact.getPhone());
                    jsonArray.put(jsonObject);
                }
            } catch (JSONException e) {
                e.printStackTrace();
            }
            final String json = jsonArray.toString();
            /**
             *  A WebView method was called on thread 'JavaBridge'. All WebView methods must be called on the same thread
             *  简单来说就是html里调用java方法和java调用js方法必须在同一线程
            */
            mWebview.post(new Runnable() {
                @Override
                public void run() {
                    mWebview.loadUrl("javascript:show('" + json + "')");//通过webview调用js 方法
                }
            });
        }
    }
}

这里写图片描述

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐