• Android中解析XML

    2016-07-22 12:10:39
    XML在各种开发中都广泛应用,Android也不例外。作为承载数据的一个重要角色,如何读写XML成为Android开发中一项重要的技能。今天就由我向大家介绍一下在Android平台下几种常见的XML解析和创建的方法。在Android中,...

    XML在各种开发中都广泛应用,Android也不例外。作为承载数据的一个重要角色,如何读写XML成为Android开发中一项重要的技能。今天就由我向大家介绍一下在Android平台下几种常见的XML解析和创建的方法。

    在Android中,常见的XML解析器分别为SAX解析器、DOM解析器和PULL解析器,下面,我将一一向大家详细介绍。

    SAX解析器:

    SAX(Simple API for XML)解析器是一种基于事件的解析器,它的核心是事件处理模式,主要是围绕着事件源以及事件处理器来工作的。当事件源产生事件后,调用事件处理器相应的处理方法,一个事件就可以得到处理。在事件源调用事件处理器中特定方法的时候,还要传递给事件处理器相应事件的状态信息,这样事件处理器才能够根据提供的事件信息来决定自己的行为。

    SAX解析器的优点是解析速度快,占用内存少。非常适合在Android移动设备中使用。

    DOM解析器:

    DOM是基于树形结构的的节点或信息片段的集合,允许开发人员使用DOM API遍历XML树、检索所需数据。分析该结构通常需要加载整个文档和构造树形结构,然后才可以检索和更新节点信息。

    由于DOM在内存中以树形结构存放,因此检索和更新效率会更高。但是对于特别大的文档,解析和加载整个文档将会很耗资源。

    PULL解析器:

    PULL解析器的运行方式和SAX类似,都是基于事件的模式。不同的是,在PULL解析过程中,我们需要自己获取产生的事件然后做相应的操作,而不像SAX那样由处理器触发一种事件的方法,执行我们的代码。PULL解析器小巧轻便,解析速度快,简单易用,非常适合在Android移动设备中使用,Android系统内部在解析各种XML时也是用PULL解析器。

    以上三种解析器,都是非常实用的解析器,我将会一一介绍。我们将会使用这三种解析技术完成一项共同的任务。

    我们新建一个项目,项目结构如下:

    我会在项目的assets目录中放置一个XML文档books.xml,内容如下:

    <?xml version="1.0" encoding="utf-8"?>
    <books>
        <book>
            <id>1001</id>
            <name>Thinking In Java</name>
            <price>80.00</price>
        </book>
        <book>
            <id>1002</id>
            <name>Core Java</name>
            <price>90.00</price>
        </book>
        <book>
            <id>1003</id>
            <name>Hello, Andriod</name>
            <price>100.00</price>
        </book>
    </books>
    然后我们分别使用以上三种解析技术解析文档,得到一个List<Book>的对象,先来看一下Book.java的代码:

    package com.scott.xml.model;
    
    public class Book {
        private int id;
        private String name;
        private float price;
    
        public int getId() {
            return id;
        }
    
        public void setId(int id) {
            this.id = id;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public float getPrice() {
            return price;
        }
    
        public void setPrice(float price) {
            this.price = price;
        }
    
        @Override
        public String toString() {
            return "id:" + id + ", name:" + name + ", price:" + price;
        }
    }
    最后,我们还要把这个集合对象中的数据生成一个新的XML文档,如图:

    生成的XML结构跟原始文档略有不同,是下面这种格式:

    <?xml version="1.0" encoding="UTF-8"?>
    <books>
        <book id="1001">
            <name>Thinking In Java</name>
            <price>80.0</price>
        </book>
        <book id="1002">
            <name>Core Java</name>
            <price>90.0</price>
        </book>
        <book id="1003">
            <name>Hello, Andriod</name>
            <price>100.0</price>
        </book>
    </books>
    接下来,就该介绍操作过程了,我们先为解析器定义一个BookParser接口,每种类型的解析器需要实现此接口。BookParser.java代码如下:

    package com.scott.xml.parser;
    
    import java.io.InputStream;
    import java.util.List;
    
    import com.scott.xml.model.Book;
    
    public interface BookParser {
        /**
         * 解析输入流 得到Book对象集合
         * @param is
         * @return
         * @throws Exception
         */
        public List<Book> parse(InputStream is) throws Exception;
    
        /**
         * 序列化Book对象集合 得到XML形式的字符串
         * @param books
         * @return
         * @throws Exception
         */
        public String serialize(List<Book> books) throws Exception;
    }
    好了,我们就该一个一个的实现该接口,完成我们的解析过程。

    使用SAX解析器:

    SaxBookParser.java代码如下:

    package com.scott.xml.parser;
    
    import java.io.InputStream;
    import java.io.StringWriter;
    import java.util.ArrayList;
    import java.util.List;
    
    import javax.xml.parsers.SAXParser;
    import javax.xml.parsers.SAXParserFactory;
    import javax.xml.transform.OutputKeys;
    import javax.xml.transform.Result;
    import javax.xml.transform.Transformer;
    import javax.xml.transform.TransformerFactory;
    import javax.xml.transform.sax.SAXTransformerFactory;
    import javax.xml.transform.sax.TransformerHandler;
    import javax.xml.transform.stream.StreamResult;
    
    import org.xml.sax.Attributes;
    import org.xml.sax.SAXException;
    import org.xml.sax.helpers.AttributesImpl;
    import org.xml.sax.helpers.DefaultHandler;
    
    import com.scott.xml.model.Book;
    
    public class SaxBookParser implements BookParser {
    
        @Override
        public List<Book> parse(InputStream is) throws Exception {
            SAXParserFactory factory = SAXParserFactory.newInstance();	//取得SAXParserFactory实例
            SAXParser parser = factory.newSAXParser();					//从factory获取SAXParser实例
            MyHandler handler = new MyHandler();						//实例化自定义Handler
            parser.parse(is, handler);									//根据自定义Handler规则解析输入流
            return handler.getBooks();
        }
    
        @Override
        public String serialize(List<Book> books) throws Exception {
            SAXTransformerFactory factory = (SAXTransformerFactory) TransformerFactory.newInstance();//取得SAXTransformerFactory实例
            TransformerHandler handler = factory.newTransformerHandler();			//从factory获取TransformerHandler实例
            Transformer transformer = handler.getTransformer();						//从handler获取Transformer实例
            transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");			// 设置输出采用的编码方式
            transformer.setOutputProperty(OutputKeys.INDENT, "yes");				// 是否自动添加额外的空白
            transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no");	// 是否忽略XML声明
    
            StringWriter writer = new StringWriter();
            Result result = new StreamResult(writer);
            handler.setResult(result);
    
            String uri = "";	//代表命名空间的URI 当URI无值时 须置为空字符串
            String localName = "";	//命名空间的本地名称(不包含前缀) 当没有进行命名空间处理时 须置为空字符串
    
            handler.startDocument();
            handler.startElement(uri, localName, "books", null);
    
            AttributesImpl attrs = new AttributesImpl();	//负责存放元素的属性信息
            char[] ch = null;
            for (Book book : books) {
                attrs.clear();	//清空属性列表
                attrs.addAttribute(uri, localName, "id", "string", String.valueOf(book.getId()));//添加一个名为id的属性(type影响不大,这里设为string)
                handler.startElement(uri, localName, "book", attrs);	//开始一个book元素 关联上面设定的id属性
    
                handler.startElement(uri, localName, "name", null);	//开始一个name元素 没有属性
                ch = String.valueOf(book.getName()).toCharArray();
                handler.characters(ch, 0, ch.length);	//设置name元素的文本节点
                handler.endElement(uri, localName, "name");
    
                handler.startElement(uri, localName, "price", null);//开始一个price元素 没有属性
                ch = String.valueOf(book.getPrice()).toCharArray();
                handler.characters(ch, 0, ch.length);	//设置price元素的文本节点
                handler.endElement(uri, localName, "price");
    
                handler.endElement(uri, localName, "book");
            }
            handler.endElement(uri, localName, "books");
            handler.endDocument();
    
            return writer.toString();
        }
    
        //需要重写DefaultHandler的方法
        private class MyHandler extends DefaultHandler {
    
            private List<Book> books;
            private Book book;
            private StringBuilder builder;
    
            //返回解析后得到的Book对象集合
            public List<Book> getBooks() {
                return books;
            }
    
            @Override
            public void startDocument() throws SAXException {
                super.startDocument();
                books = new ArrayList<Book>();
                builder = new StringBuilder();
            }
    
            @Override
            public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
                super.startElement(uri, localName, qName, attributes);
                if (localName.equals("book")) {
                    book = new Book();
                }
                builder.setLength(0);	//将字符长度设置为0 以便重新开始读取元素内的字符节点
            }
    
            @Override
            public void characters(char[] ch, int start, int length) throws SAXException {
                super.characters(ch, start, length);
                builder.append(ch, start, length);	//将读取的字符数组追加到builder中
            }
    
            @Override
            public void endElement(String uri, String localName, String qName) throws SAXException {
                super.endElement(uri, localName, qName);
                if (localName.equals("id")) {
                    book.setId(Integer.parseInt(builder.toString()));
                } else if (localName.equals("name")) {
                    book.setName(builder.toString());
                } else if (localName.equals("price")) {
                    book.setPrice(Float.parseFloat(builder.toString()));
                } else if (localName.equals("book")) {
                    books.add(book);
                }
            }
        }
    }
    代码中,我们定义了自己的事件处理逻辑,重写了DefaultHandler的几个重要的事件方法。下面我为大家着重介绍一下DefaultHandler的相关知识。DefaultHandler是一个事件处理器,可以接收解析器报告的所有事件,处理所发现的数据。它实现了EntityResolver接口、DTDHandler接口、ErrorHandler接口和ContentHandler接口。这几个接口代表不同类型的事件处理器。我们着重介绍一下ContentHandler接口。结构如图:

    这几个比较重要的方法已被我用红线标注,DefaultHandler实现了这些方法,但在方法体内没有做任何事情,因此我们在使用时必须覆写相关的方法。最重要的是startElement方法、characters方法和endElement方法。当执行文档时遇到起始节点,startElement方法将会被调用,我们可以获取起始节点相关信息;然后characters方法被调用,我们可以获取节点内的文本信息;最后endElement方法被调用,我们可以做收尾的相关操作。

    最后,我们需要调用SAX解析程序,这个步骤在MainActivity中完成:

    package com.scott.xml;
    
    import java.io.FileOutputStream;
    import java.io.InputStream;
    import java.util.List;
    
    import android.app.Activity;
    import android.content.Context;
    import android.os.Bundle;
    import android.util.Log;
    import android.view.View;
    import android.widget.Button;
    
    import com.scott.xml.model.Book;
    import com.scott.xml.parser.BookParser;
    import com.scott.xml.parser.SaxBookParser;
    
    public class MainActivity extends Activity {
    
        private static final String TAG = "XML";
    
        private BookParser parser;
        private List<Book> books;
    
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.main);
    
            Button readBtn = (Button) findViewById(R.id.readBtn);
            Button writeBtn = (Button) findViewById(R.id.writeBtn);
    
            readBtn.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    try {
                        InputStream is = getAssets().open("books.xml");
                        parser = new SaxBookParser();  //创建SaxBookParser实例
                        books = parser.parse(is);  //解析输入流
                        for (Book book : books) {
                            Log.i(TAG, book.toString());
                        }
                    } catch (Exception e) {
                        Log.e(TAG, e.getMessage());
                    }
                }
            });
            writeBtn.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    try {
                        String xml = parser.serialize(books);  //序列化
                        FileOutputStream fos = openFileOutput("books.xml", Context.MODE_PRIVATE);
                        fos.write(xml.getBytes("UTF-8"));
                    } catch (Exception e) {
                        Log.e(TAG, e.getMessage());
                    }
                }
            });
        }
    }
    界面就两个按钮,顺便给大家贴上:

     

    点击“readXML”按钮,将会调用SAX解析器解析文档,并在日志台打印相关信息:

    然后再点击“writeXML”按钮,将会在该应用包下的files目录生成一个books.xml文件:

    使用DOM解析器:

    DomBookParser.java代码如下:

    package com.scott.xml.parser;
    
    import java.io.InputStream;
    import java.io.StringWriter;
    import java.util.ArrayList;
    import java.util.List;
    
    import javax.xml.parsers.DocumentBuilder;
    import javax.xml.parsers.DocumentBuilderFactory;
    import javax.xml.transform.OutputKeys;
    import javax.xml.transform.Result;
    import javax.xml.transform.Source;
    import javax.xml.transform.Transformer;
    import javax.xml.transform.TransformerFactory;
    import javax.xml.transform.dom.DOMSource;
    import javax.xml.transform.stream.StreamResult;
    
    import org.w3c.dom.Document;
    import org.w3c.dom.Element;
    import org.w3c.dom.Node;
    import org.w3c.dom.NodeList;
    
    import com.scott.xml.model.Book;
    
    public class DomBookParser implements BookParser {
    
        @Override
        public List<Book> parse(InputStream is) throws Exception {
            List<Book> books = new ArrayList<Book>();
            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();	//取得DocumentBuilderFactory实例
            DocumentBuilder builder = factory.newDocumentBuilder();	//从factory获取DocumentBuilder实例
            Document doc = builder.parse(is);	//解析输入流 得到Document实例
            Element rootElement = doc.getDocumentElement();
            NodeList items = rootElement.getElementsByTagName("book");
            for (int i = 0; i < items.getLength(); i++) {
                Book book = new Book();
                Node item = items.item(i);
                NodeList properties = item.getChildNodes();
                for (int j = 0; j < properties.getLength(); j++) {
                    Node property = properties.item(j);
                    String nodeName = property.getNodeName();
                    if (nodeName.equals("id")) {
                        book.setId(Integer.parseInt(property.getFirstChild().getNodeValue()));
                    } else if (nodeName.equals("name")) {
                        book.setName(property.getFirstChild().getNodeValue());
                    } else if (nodeName.equals("price")) {
                        book.setPrice(Float.parseFloat(property.getFirstChild().getNodeValue()));
                    }
                }
                books.add(book);
            }
            return books;
        }
    
        @Override
        public String serialize(List<Book> books) throws Exception {
            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
            DocumentBuilder builder = factory.newDocumentBuilder();
            Document doc = builder.newDocument();	//由builder创建新文档
    
            Element rootElement = doc.createElement("books");
    
            for (Book book : books) {
                Element bookElement = doc.createElement("book");
                bookElement.setAttribute("id", book.getId() + "");
    
                Element nameElement = doc.createElement("name");
                nameElement.setTextContent(book.getName());
                bookElement.appendChild(nameElement);
    
                Element priceElement = doc.createElement("price");
                priceElement.setTextContent(book.getPrice() + "");
                bookElement.appendChild(priceElement);
    
                rootElement.appendChild(bookElement);
            }
    
            doc.appendChild(rootElement);
    
            TransformerFactory transFactory = TransformerFactory.newInstance();//取得TransformerFactory实例
            Transformer transformer = transFactory.newTransformer();	//从transFactory获取Transformer实例
            transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");			// 设置输出采用的编码方式
            transformer.setOutputProperty(OutputKeys.INDENT, "yes");				// 是否自动添加额外的空白
            transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no");	// 是否忽略XML声明
    
            StringWriter writer = new StringWriter();
    
            Source source = new DOMSource(doc);	//表明文档来源是doc
            Result result = new StreamResult(writer);//表明目标结果为writer
            transformer.transform(source, result);	//开始转换
    
            return writer.toString();
        }
    
    }
    然后再MainActivity中只需改一个地方:

    readBtn.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            try {
                InputStream is = getAssets().open("books.xml");
    //		    parser = new SaxBookParser();
                parser = new DomBookParser();
                books = parser.parse(is);
                for (Book book : books) {
                    Log.i(TAG, book.toString());
                }
            } catch (Exception e) {
                Log.e(TAG, e.getMessage());
            }
        }
    });

    执行结果是一样的。

    使用PULL解析器:

    PullBookParser.java代码如下:

    package com.scott.xml.parser;
    
    import java.io.InputStream;
    import java.io.StringWriter;
    import java.util.ArrayList;
    import java.util.List;
    
    import org.xmlpull.v1.XmlPullParser;
    import org.xmlpull.v1.XmlSerializer;
    
    import android.util.Xml;
    
    import com.scott.xml.model.Book;
    
    public class PullBookParser implements BookParser {
    
        @Override
        public List<Book> parse(InputStream is) throws Exception {
            List<Book> books = null;
            Book book = null;
    
    //		XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
    //		XmlPullParser parser = factory.newPullParser();
    
            XmlPullParser parser = Xml.newPullParser();	//由android.util.Xml创建一个XmlPullParser实例
            parser.setInput(is, "UTF-8");				//设置输入流 并指明编码方式
    
            int eventType = parser.getEventType();
            while (eventType != XmlPullParser.END_DOCUMENT) {
                switch (eventType) {
                    case XmlPullParser.START_DOCUMENT:
                        books = new ArrayList<Book>();
                        break;
                    case XmlPullParser.START_TAG:
                        if (parser.getName().equals("book")) {
                            book = new Book();
                        } else if (parser.getName().equals("id")) {
                            eventType = parser.next();
                            book.setId(Integer.parseInt(parser.getText()));
                        } else if (parser.getName().equals("name")) {
                            eventType = parser.next();
                            book.setName(parser.getText());
                        } else if (parser.getName().equals("price")) {
                            eventType = parser.next();
                            book.setPrice(Float.parseFloat(parser.getText()));
                        }
                        break;
                    case XmlPullParser.END_TAG:
                        if (parser.getName().equals("book")) {
                            books.add(book);
                            book = null;
                        }
                        break;
                }
                eventType = parser.next();
            }
            return books;
        }
    
        @Override
        public String serialize(List<Book> books) throws Exception {
    //		XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
    //		XmlSerializer serializer = factory.newSerializer();
    
            XmlSerializer serializer = Xml.newSerializer();	//由android.util.Xml创建一个XmlSerializer实例
            StringWriter writer = new StringWriter();
            serializer.setOutput(writer);	//设置输出方向为writer
            serializer.startDocument("UTF-8", true);
            serializer.startTag("", "books");
            for (Book book : books) {
                serializer.startTag("", "book");
                serializer.attribute("", "id", book.getId() + "");
    
                serializer.startTag("", "name");
                serializer.text(book.getName());
                serializer.endTag("", "name");
    
                serializer.startTag("", "price");
                serializer.text(book.getPrice() + "");
                serializer.endTag("", "price");
    
                serializer.endTag("", "book");
            }
            serializer.endTag("", "books");
            serializer.endDocument();
    
            return writer.toString();
        }
    }
    然后再对MainActivity做以下更改:

    readBtn.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            try {
                InputStream is = getAssets().open("books.xml");
    //		    parser = new SaxBookParser();
    //			parser = new DomBookParser();
                parser = new PullBookParser();
                books = parser.parse(is);
                for (Book book : books) {
                    Log.i(TAG, book.toString());
                }
            } catch (Exception e) {
                Log.e(TAG, e.getMessage());
            }
        }
    });
    和其他两个执行结果都一样。

    对于这三种解析器各有优点,我个人比较倾向于PULL解析器,因为SAX解析器操作起来太笨重,DOM不适合文档较大,内存较小的场景,唯有PULL轻巧灵活,速度快,占用内存小,使用非常顺手。读者也可以根据自己的喜好选择相应的解析技术。

    展开全文
  • 如下图所示,在res文件夹处右键单击,选择“new”,再选择“Androidresource ...这样就新建好了一个xml文件夹,如果还需要在xml文件夹中新建一个xml文件,在“xml”文件夹处右键单击,选择“new”,再选择“File...

    如下图所示,在res文件夹处右键单击,选择“new”,再选择 “Android resource directory ” :

     

    点击“Android resource directory”之后将会弹出一个对话框,如下图所示,选择xml:

     

    这样就新建好了一个xml文件夹,如果还需要在xml文件夹中新建一个xml文件,在“xml”文件夹处右键单击,选择“new”,再选择“File”, 不要选择“XML resource file”。如下所示:

     

    弹出对话框命名即可。

    转载于:https://www.cnblogs.com/zly1022/p/7521314.html

    展开全文
  • 1. 新建xml文件夹 如下图所示,在res文件夹处右键单击,选择“new”,再选择 “Android resource directory ” : 点击“Android resource directory”之后将会弹出一个对话框,如下图所示,选择xml: 这样就...

    1. 新建xml文件夹

    如下图所示,在res文件夹处右键单击,选择“new”,再选择
    “Android resource directory ” :

    这里写图片描述

    点击“Android resource directory”之后将会弹出一个对话框,如下图所示,选择xml:

    这里写图片描述

    这样就新建好了一个xml文件夹,如果还需要在xml文件夹中新建一个xml文件,在“xml”文件夹处右键单击,选择“new”,再选择“File”,
    不要选择“XML resource file”。如下所示:

    这里写图片描述

    弹出对话框命名即可。

    2. 新建values-21文件夹

    如下图所示:

    这里写图片描述

    点击“Android resource directory”之后将会弹出一个对话框,如下图所示,选择xml:
    这里写图片描述

    然后就可以看到velues-21文件夹,把values文件夹里面的style.xml文件复制到velues-21文件夹。

    2. 新建values-w820dp文件夹

    values-w820dp文件夹可能平时看到的不多,这个文件夹表示这个目录下的资源所要求屏幕的最小宽度是820dp。
    这个怎么创建呢?和上面的values-21文件夹类似,只不过我们需要选择不同的标签:
    这里写图片描述

    展开全文
  • Android-Xml绘图

    2016-04-19 17:12:40
    Android XML绘图XMlAndroid中可不仅仅是一个布局文件、配置列表。它甚至可以变成一张画、一张图。Bitmap声明: res/drawable/bitmap.xml<?xml version="1.0" encoding="utf-8"?> <bitmap xmlns:android=...

    Android XML绘图

    XMl在Android中可不仅仅是一个布局文件、配置列表。它甚至可以变成一张画、一张图。

    Bitmap

    声明:
    res/drawable/bitmap.xml

    <?xml version="1.0" encoding="utf-8"?>
    <bitmap xmlns:android="http://schemas.android.com/apk/res/android"
        android:src="@drawable/zjl" />

    引用

    
        <ImageView
            android:id="@+id/id_iv_zjl"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@drawable/bitmap" />

    Shape

    android的样式主要则是通过shape、selector、layer-list、level-list、style、theme等组合实现。

    一般用shape定义的xml文件存放在drawable目录下,若项目没有该目录则新建一个,而不要将它放到drawable-hdpi等目录中。

    使用shape可以自定义形状,可以定义下面四种类型的形状,通过android:shape属性指定:

    • rectangle: 矩形,默认的形状,可以画出直角矩形、圆角矩形、弧形等
    • oval: 椭圆形,用得比较多的是画正圆
    • line: 线形,可以画实线和虚线
    • ring: 环形,可以画环形进度条

    通过shape可以在XML中绘制任何形状,下面展示了Shape所支持的参数

    <shape
        xmlns:android="http://schemas.android.com/apk/res/android"
        //默认为rectangle
        android:shape=["rectangle"|"oval"|"line"|"ring"]>
        <corners //当shape="rectangle"时使用
            //半径,会被后面的单个半径属性覆盖
            android:radius="integer"
            android:topLeftRadius="integer"
            android:topRightRadius="integer"
            android:bottomLegtRadius="integer"
            android:bottomRightRadius="integer"/>
        <gradient   //渐变
            android:angle="integer"
            android:centerX="integer"
            android:centerY="integer"
            android:centerColor="color"
            android:endColor="color"
            android:gradientRadius="integer"
            android:startColor="color"
            android:type=["linear"|"radial"|"sweep"]
            android:useCenter=["true"|"false"]/>
        <padding
            android:left="integer"
            android:top="integer"
            android:right="integer"
            android:bottom="integer"/>
        <size //指定大小,一般用在ImageView配合scaleType属性使用
            android:width="integer"
            android:height="integer"/>
        <solid  //填充颜色
            android:color="color"/>
        <stroke //指定边框
            android:width="integer"
            android:color="color"
            //虚线宽度
            android:dashWidth="integer"
            //虚线间隔宽度
            android:dashGap="integer"/>
    </shape>

    rectangle

    solid: 设置形状填充的颜色,只有android:color一个属性

    android:color 填充的颜色
    

    padding: 设置内容与形状边界的内间距,可分别设置左右上下的距离

    android:left 左内间距
    android:right 右内间距
    android:top 上内间距
    android:bottom 下内间距
    

    gradient: 设置形状的渐变颜色,可以是线性渐变、辐射渐变、扫描性渐变

    android:type 渐变的类型
        linear 线性渐变,默认的渐变类型
        radial 放射渐变,设置该项时,android:gradientRadius也必须设置
        sweep 扫描性渐变
    android:startColor 渐变开始的颜色
    android:endColor 渐变结束的颜色
    android:centerColor 渐变中间的颜色
    android:angle 渐变的角度,线性渐变时才有效,必须是45的倍数,0表示从左到右,90表示从下到上
    android:centerX 渐变中心的相对X坐标,放射渐变时才有效,在0.0到1.0之间,默认为0.5,表示在正中间
    android:centerY 渐变中心的相对X坐标,放射渐变时才有效,在0.0到1.0之间,默认为0.5,表示在正中间
    android:gradientRadius 渐变的半径,只有渐变类型为radial时才使用
    android:useLevel 如果为true,则可在LevelListDrawable中使用
    

    corners: 设置圆角,只适用于rectangle类型,可分别设置四个角不同半径的圆角,当设置的圆角半径很大时,比如200dp,就可变成弧形边了

    android:radius 圆角半径,会被下面每个特定的圆角属性重写
    android:topLeftRadius 左上角的半径
    android:topRightRadius 右上角的半径
    android:bottomLeftRadius 左下角的半径
    android:bottomRightRadius 右下角的半径
    

    stroke: 设置描边,可描成实线或虚线。

    android:color 描边的颜色
    android:width 描边的宽度
    android:dashWidth 设置虚线时的横线长度
    android:dashGap 设置虚线时的横线之间的距离
    

    <?xml version="1.0" encoding="utf-8"?><!-- android:shape指定形状类型,默认为rectangle -->
    <shape xmlns:android="http://schemas.android.com/apk/res/android"
        android:shape="rectangle">
        <!-- solid指定形状的填充色,只有android:color一个属性 -->
        <solid android:color="#2F90BD" />
    
    
        <!-- padding设置内容区域离边界的间距 -->
        <padding
            android:bottom="12dp"
            android:left="12dp"
            android:right="12dp"
            android:top="12dp" />
    
    
        <!-- corners设置圆角,只适用于rectangle -->
        <corners android:radius="200dp" />
    
    
        <!-- stroke设置描边 -->
        <stroke
            android:width="2dp"
            android:color="@android:color/darker_gray"
            android:dashGap="4dp"
            android:dashWidth="4dp" />
    
    
    </shape>

    接着在要使用的view里引用就可以了,例如本例中用做TextView的background:

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="8dp"
        android:text="加了虚线描边的矩形"
        android:textSize="16sp"
        android:textColor="@android:color/white"
        android:background="@drawable/bg_rectangle_with_stroke_dash" />

    效果图:

    这里写图片描述

    全部效果图:

    这里写图片描述

    oval

    oval用来画椭圆,而在实际应用中,更多是画正圆,比如消息提示,圆形按钮等

    这里写图片描述

    上面的效果图应用了solid、padding、stroke、gradient、size几个特性。size是用来设置形状大小的,如下:

    • size: 设置形状默认的大小,可设置宽度和高度
      android:width 宽度
      android:height 高度

    数字0是默认的椭圆,只加了solid填充颜色,
    数字1则加了上下左右4dp的padding,
    后面的数字都是正圆,是通过设置size的同样大小的宽高实现的,也可以通过设置控件的宽高一致大小来实现。
    数字3加了描边,
    数字4是镂空描边,
    数字5是虚线描边,
    数字6用了radial渐变。注意,使用radial渐变时,必须指定渐变的半径,即android:gradientRadius属性。

    以下是渐变的代码实现,文件为bg_oval_with_gradient.xml:

    <?xml version="1.0" encoding="utf-8"?>
    <shape xmlns:android="http://schemas.android.com/apk/res/android"
        android:shape="oval">
    
    
        <!-- padding设置内间距 -->
        <padding
            android:bottom="4dp"
            android:left="4dp"
            android:right="4dp"
            android:top="4dp" />
        <!-- size设置形状的大小 -->
        <size
            android:width="40dp"
            android:height="40dp" />
        <!-- gradient设置渐变 -->
        <gradient
            android:endColor="#98FB98"
            android:gradientRadius="40dp"
            android:startColor="#D1EEEE"
            android:type="radial" />
    
    </shape>

    引用的代码:

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:layout_margin="8dp"
        android:text="6"
        android:textSize="20sp"
        android:textColor="@android:color/black"
        android:background="@drawable/bg_oval_with_gradient" />

    line

    这里写图片描述

    line主要用于画分割线,是通过stroke和size特性组合来实现的,先看虚线的代码:

    <?xml version="1.0" encoding="utf-8"?>
    <shape xmlns:android="http://schemas.android.com/apk/res/android"
        android:shape="line">
    
        <stroke
            android:width="1dp"
            android:color="#FF0000" />
        <!-- 虚线的高度 -->
        <size android:height="4dp" />
    
    </shape>

    画线时,有几点特性必须要知道的:

    • 只能画水平线,画不了竖线;
    • 线的高度是通过stroke的android:width属性设置的;
    • size的android:height属性定义的是整个形状区域的高度;
    • size的height必须大于stroke的width,否则,线无法显示;
    • 线在整个形状区域中是居中显示的;
    • 线左右两边会留有空白间距,线越粗,空白越大;
    • 引用虚线的view需要添加属性android:layerType,值设为”software”,否则显示不了虚线。

    引用:

     <ImageView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:background="@drawable/bg_line_with_solid" />

    ring

    首先,shape根元素有些属性只适用于ring类型,先过目下这些属性吧:

    • android:innerRadius 内环的半径
    • android:innerRadiusRatio 浮点型,以环的宽度比率来表示内环的半径,默认为3,表示内环半径为环的宽度除以3,该值会被 android:innerRadius覆盖
    • android:thickness 环的厚度
    • android:thicknessRatio 浮点型,以环的宽度比率来表示环的厚度,默认为9,表示环的厚度为环的宽度除以9,该值会被-android:thickness覆盖
    • android:useLevel 一般为false,否则可能环形无法显示,只有作为LevelListDrawable使用时才设为true

    这里写图片描述

    第一个图只添加了solid;
    第二个图只添加了gradient,类型为sweep;
    第三个图只添加了stroke;
    第四个图添加了gradient和stroke两项特性。

    以下为第四个图的代码:

    <?xml version="1.0" encoding="utf-8"?>
    <shape xmlns:android="http://schemas.android.com/apk/res/android"
        android:innerRadiusRatio="3"
        android:shape="ring"
        android:thicknessRatio="9"
        android:useLevel="false">
        <gradient
            android:endColor="#2F90BD"
            android:startColor="#FFFFFF"
            android:type="sweep" />
        <stroke
            android:width="1dp"
            android:color="@android:color/black" />
    </shape>

    如果想让这个环形旋转起来,变成可用的进度条,则只要在shape外层包多一个rotate元素就可以了。

    <?xml version="1.0" encoding="utf-8"?>
    <rotate xmlns:android="http://schemas.android.com/apk/res/android"
        android:fromDegrees="0"
        android:pivotX="50%"
        android:pivotY="50%"
        android:toDegrees="1080.0">
        <shape
            android:innerRadiusRatio="3"
            android:shape="ring"
            android:thicknessRatio="8"
            android:useLevel="false">
            <gradient
                android:endColor="#FF0000"
                android:startColor="#FFFFFF"
                android:type="sweep" />
        </shape>
    
    </rotate>

    引用
    android:indeterminateDrawable

      <ProgressBar
            android:layout_width="50dp"
            android:layout_height="50dp"
            android:layout_margin="8dp"
            android:indeterminate="false"
            android:indeterminateDrawable="@drawable/bg_ring_with_gradient_rotate" />
    

    Layer

    本案例参考刚哥的博客layer-list
    向前辈学习~

    这里写图片描述

    效果分析:
    TAB的背景效果 + 带阴影的圆角矩形

    在这里我们没有用到任何的图片,完全是依靠 shape+selector+layer-list完成。

    使用layer-list可以将多个drawable按照顺序层叠在一起显示,像上图中的Tab,是由一个红色的层加一个白色的层叠在一起显示的结果,阴影的圆角矩形则是由一个灰色的圆角矩形叠加上一个白色的圆角矩形。


    Tab背景的代码:

    第一种实现方式:
    bg_tab_selector.xml

    <?xml version="1.0" encoding="utf-8"?>
    <selector xmlns:android="http://schemas.android.com/apk/res/android">
    
    
        <!-- 第一种加载方式 -->
        <item android:drawable="@drawable/bg_tab_selected"
            android:state_checked="true" />
        <item android:drawable="@drawable/bg_tab_unselected" />
    
    </selector>

    bg_tab_selected.xml

    <?xml version="1.0" encoding="utf-8"?>
    <layer-list xmlns:android="http://schemas.android.com/apk/res/android">
        <!-- 红色底 -->
        <item>
            <color android:color="#E4007F" />
        </item>
        <!-- 白色背景 -->
        <item
            android:bottom="4dp"
            android:drawable="@android:color/white" />
    </layer-list>

    bg_tab_unselected.xml

    <?xml version="1.0" encoding="utf-8"?>
    <layer-list xmlns:android="http://schemas.android.com/apk/res/android">
        <!-- 红色底 -->
        <item>
            <color android:color="#E4007F" />
        </item>
        <!-- 白色背景 -->
        <item
            android:bottom="1dp"
            android:drawable="@android:color/white" />
    </layer-list>

    第二种实现方式 (只是把第一种的实现方式写到一个文件里)

    bg_tab_selected.xml

    <?xml version="1.0" encoding="utf-8"?>
    <selector xmlns:android="http://schemas.android.com/apk/res/android">
    
    
        <!-- 第一种加载方式 -->
        <!--   <item android:drawable="@drawable/bg_tab_selected"
          android:state_checked="true" />>-->
        <!--  <item android:drawable="@drawable/bg_tab_unselected" />-->
    
    
        <!-- 第二种加载方式 -->
    
        <!--选中的时候-->
        <item android:state_checked="true">
            <layer-list>
                <!-- 红色底 -->
                <item>
                    <color android:color="#E4007F" />
                </item>
                <!-- 白色背景 -->
                <item android:bottom="4dp" android:drawable="@android:color/white" />
            </layer-list>
    
        </item>
    
        <!--非选中的时候-->
        <item>
            <layer-list>
                <item>
                    <color android:color="#E4007F" />
                </item>
                <item android:bottom="1dp">
                    <color android:color="@android:color/white" />
                </item>
            </layer-list>
        </item>
    
    
    </selector>

    文本部分:
    res/color/text_tab_selector.xml

    <?xml version="1.0" encoding="utf-8"?>
    <selector xmlns:android="http://schemas.android.com/apk/res/android">
        <item android:color="#E4007F" android:state_checked="true" />
        <item android:color="#E4007F" android:state_selected="true" />
        <item android:color="@android:color/black" />
    </selector>

    带阴影的圆角矩形:

    bg_shadow_corners_rectangle.xml

    <?xml version="1.0" encoding="utf-8"?>
    <layer-list xmlns:android="http://schemas.android.com/apk/res/android"
        android:paddingBottom="16dp"
        android:paddingMode="stack"
        android:paddingTop="16dp">
        <!-- 灰色阴影 -->
        <item
            android:left="2dp"
            android:top="4dp">
            <shape>
                <solid android:color="@android:color/darker_gray" />
                <corners android:radius="10dp" />
            </shape>
        </item>
        <!-- 白色前景 -->
        <item
            android:bottom="4dp"
            android:right="2dp">
            <shape>
                <solid android:color="#FFFFFF" />
                <corners android:radius="10dp" />
            </shape>
        </item>
    </layer-list>

    引用:

    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="#E0EEEE"
        android:orientation="vertical">
    
        <RadioGroup
            android:layout_width="match_parent"
            android:layout_height="48dp"
            android:gravity="center"
            android:orientation="horizontal">
    
            <RadioButton
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:layout_weight="1"
                android:background="@drawable/bg_tab_selector"
                android:button="@null"
                android:gravity="center"
                android:text="TAB1"
                android:textColor="@color/text_tab_selector" />
    
            <RadioButton
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:layout_weight="1"
                android:background="@drawable/bg_tab_selector"
                android:button="@null"
                android:gravity="center"
                android:text="TAB2"
                android:textColor="@color/text_tab_selector" />
    
            <RadioButton
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:layout_weight="1"
                android:background="@drawable/bg_tab_selector"
                android:button="@null"
                android:gravity="center"
                android:text="TAB3"
                android:textColor="@color/text_tab_selector" />
        </RadioGroup>
    
        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_margin="8dp"
            android:background="@drawable/bg_shadow_corners_rectangle"
            android:gravity="center"
            android:padding="16dp"
            android:text="带阴影的圆角矩形-1" />
    
    
        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_margin="8dp"
            android:background="@drawable/bg_shadow_corners_rectangle"
            android:gravity="center"
            android:padding="16dp"
            android:text="带阴影的圆角矩形-2" />
    </LinearLayout>

    总结:

    • 从上面的示例代码可以看到,layer-list可以作为根节点,也可以作为selector中item的子节点。
    • layer-list可以添加多个item子节点,每个item子节点对应一个drawable资源,按照item从上到下的顺序叠加在一起,再通过设置每个item的偏移量就可以看到阴影等效果了
    • layer-list的item可以通过下面四个属性设置偏移量:

    android:top 顶部的偏移量
    android:bottom 底部的偏移量
    android:left 左边的偏移量
    android:right 右边的偏移量

    • 这四个偏移量和控件的margin设置差不多,都是外间距的效果。如何不设置偏移量,前面的图层就完全挡住了后面的图层,从而也看不到后面的图层效果了。比如上面的例子,Tab背景中的白色背景设置了android:bottom之后才能看到一点红色背景。那么如果偏移量设为负值会怎么样呢?经过验证,偏移超出的部分会被截掉而看不到,不信可以自己试一下。有时候这很有用,比如当我想显示一个半圆的时候。

    另外,关于item的用法,也做下总结:

    • 根节点不同时,可设置的属性是会不同的,比如selector下,可以设置一些状态属性,而在layer-list下,可以设置偏移量;
    • 就算父节点同样是selector,放在drawable目录和放在color目录下可用的属性也会不同,比如drawable目录下可用的属性为android:drawable,在color目录下可用的属性为android:color;
    • item的子节点可以为任何类型的drawable类标签,除了上面例子中的shape、color、layer-list,也可以是selector,还有其他没讲过的bitmap、clip、scale、inset、transition、rotate、animated-rotate、lever-list等等。

    Selector

    之前的博文底部导航栏的几种实现方式底部是采用了selector样式,也可以看下。

    先看下最后的实现效果:

    这里写图片描述
    下面切入正题:

    shape虽然可以自定义矩形、圆形、线形和环形,以及有哪些需要注意的地方。不过,shape只能定义单一的形状,而实际应用中,很多地方比如按钮、Tab、ListItem等都是不同状态有不同的展示形状。举个例子,一个按钮的背景,默认时是一个形状,按下时是一个形状,不可操作时又是另一个形状。有时候,不同状态下改变的不只是背景、图片等,文字颜色也会相应改变。而要处理这些不同状态下展示什么的问题,就要用selector来实现了。

    selector标签,可以添加一个或多个item子标签,而相应的状态是在item标签中定义的。

    定义的xml文件可以作为两种资源使用:drawable和color。

    作为drawable资源使用时,一般和shape一样放于drawable目录下,item必须指定android:drawable属性
    作为color资源使用时,则放于color目录下,item必须指定android:color属性。

    可设置的状态:
    这里写图片描述

    如果不愿意手工编写,可以在Android Studio使用插件android-selector-chapek,但是图片的命名规则需要按照规范才可以自动生成。

    注意事项:

    • selector作为drawable资源时,item指定android:drawable属性,并放于drawable目录下;
    • selector作为color资源时,item指定android:color属性,并放于color目录下;
    • color资源也可以放于drawable目录,引用时则用@drawable来引用,但不推荐这么做,drawable资源和color资源最好还是分开;
      android:drawable属性除了引用@drawable资源,也可以引用@color颜色值;但android:color只能引用@color;
    • item是从上往下匹配的,如果匹配到一个item那它就将采用这个item,而不是采用最佳匹配的规则;所以设置默认的状态,一定要写在最后,如果写在前面,则后面所有的item都不会起作用了。

    另外,selector标签下有两个比较有用的属性要说一下,添加了下面两个属性之后,则会在状态改变时出现淡入淡出效果,但必须在API Level 11及以上才支持:

    android:enterFadeDuration 状态改变时,新状态展示时的淡入时间,以毫秒为单位
    android:exitFadeDuration 状态改变时,旧状态消失时的淡出时间,以毫秒为单位

    最后,关于ListView的ListItem样式,有两种设置方式,一种是在ListView标签里设置android:listSelector属性,另一种是在ListItem的布局layout里设置android:background。

    但是,这两种设置的结果却有着不同。同时,使用ListView时也有些其他需要注意的地方,总结如下:

    • android:listSelector设置的ListItem默认背景是透明的,不管你在selector里怎么设置都无法改变它的背景。所以,如果想改ListItem的默认背景,只能通过第二种方式,在ListItem的布局layout里设置android:background。
    • 当触摸点击ListItem时,第一种设置方式下,state_pressed、state_focused和state_window_focused设为true时都会触发,而第二种设置方式下,只有state_pressed会触发。
    • 当ListItem里有Button或CheckBox之类的控件时,会抢占ListItem本身的焦点,导致ListItem本身的触摸点击事件会无效。那么,要解决此问题,有三种解决方案:

    • 将Button或CheckBox换成TextView或ImageView之类的控件

    • 设置Button或CheckBox之类的控件设置focusable属性为false
      设置ListItem的根布局属性- android:descendantFocusability=”blocksDescendants”

    第三种是最方便,也是推荐的方式,它会将ListItem根布局下的所有子控件都设置为不能获取焦点。android:descendantFocusability属性的值有三种,其中,ViewGroup是指设置该属性的View,本例中就是ListItem的根布局:

    • beforeDescendants:ViewGroup会优先其子类控件而获取到焦点
    • afterDescendants:ViewGroup只有当其子类控件不需要获取焦点时才获取焦点
    • blocksDescendants:ViewGroup会覆盖子类控件而直接获得焦点

    shape layer-list selector中的内容学习自Keegan小钢的文章,感谢前辈 受益匪浅~

    展开全文
  • 今天使用eclipse新建Android项目后,编写xml布局文件时想预览一下效果, 结果无法显示,并且报错:The following classes could not be instantiated: - android.support.v7.internal.app.WindowDecorActionBar ` ...

    今天使用eclipse新建Android项目后,编写xml布局文件时想预览一下效果,
    结果无法显示,并且报错:The following classes could not be instantiated:
    - android.support.v7.internal.app.WindowDecorActionBar `
    经过一方折腾和查阅资料,记录一下解决此问题的方法:
    方法1.在项目res的value或者values-XX目录下的styles.xml,找到属性:
    parent=”Theme.AppCompat.Light.DarkActionBar,
    将其修改为:parent=”Base.Theme.AppCompat.Light.DarkActionBar”,
    即在原来的parent属性中添加了”Base.”前缀.

    方法2.更改api版本:在xml布局预览界面,将api版本更改为低版本。(一般不建议这样做)
    如图:
    更改api

    方法3.跟方法1类似 ,只是把parent的值改成其他的,
    如改成:parent=”Theme.AppCompat.Light.NoActionBar”

    展开全文
  • 点击New-->Drawable resource file-->选择需要新建xml类型,默认新建的是selector文件,只需将selector更换为shape,并在头标签中添加android:shape=" ",引号中选择需要画的是Line,还是oval等,然后就可以设置不同...
  • androidXML连续动画

    2017-06-04 11:55:34
    新建一个控制图片播放顺序及时间的XML文件,这里我命名为animation.xml: 然后编写animation.xml的控制代码: 每个相当于就是一个图片 android:drawable="里面放图片资源" android:duration="每张图片播放的...
  • Android解析XML文件

    2012-08-03 23:39:46
    按计划每周更新一篇技术博文,第一篇:《Android解析XML文件》 一、在Android应用中的XML文件来源 1、本地xml文件  本地XML文件可以放在应用根目录assets文件夹、res/xml、res/raw、SDcard卡、应用的data...
  • Android项目无法新建xml

    2016-07-22 07:51:19
    eclipse导入别人的工作空间 起先报错 后经过修改可以在手机上运行 但是发现在新建 xml文件时 点击finish按键无反应 百度无果 有知道的前辈请帮帮忙 多谢
  • Android 创建与解析XML

    2013-07-13 19:55:21
    Android 创建与解析XML(一)—— 概述 分类: Android2012-04-25 12:49 84016人阅读 评论(0) 收藏 举报 xmlandroid文档apijavaj2me Android 是最常用的智能手机平台,XML 是数据交换的标准...
  • Android studio 新建xml文件后发现使用R.laout.调用,发现不存在新建xml文件,可以重启一下软件。 欢迎使用Markdown编辑器 你好! 这是你第一次使用 Markdown编辑器 所展示的欢迎页。如果你想学习如何使用...
  • 看极客学院的安卓教程,他们用eclipse创建的android XML file,可是我用的是android studio,找不到创建android XML file的选项啊
  • Eclipse中新建Android项目后src及layout文件夹没有.java和.xml文件的情况分析以及如何快速更新Android开发中需要的ADT插件,最后还向大家推荐新生的Android Studio!
  • 新建Android XML文件,类型选Drawable,根结点选selector,名字自选 2.在xml中写代码:           此种情况下,只是两张图片的切换,效果比较死板,修改成下面的代码,效果会好些:     ...
  • Android应用程序,使用动画效果,能带给用户更好的感觉,做动画可以通过XMLAndroid代码来实现。 Animation动画效果的实现可以通过两种方式进行实现,一种是tweened animation (渐变动画),另一种是frame by ...
  • 在用Android studio学习Android开发时,学到了menu布局文件。但是Android studio的结构类型中,没有menu文件夹,通过自己自主编写和查找资料,终于找到了问题所在。 错误做法 直接在res文件夹右键选择xml文件来添加...
  • 在app下面的res/drawable右键new的时候是找不到shape类型的文件的(不得不说这一点还是eclipse好一点) 所以可以直接在NEW->FILE下面建立
  • Android Studio怎么新建Android公共库模块?Android Studio中想添加公共库类似的,可以直接调用得代码,该怎么让Android公共库还可以使项目模块化? java的公共库是直接将公用代码封装jar包,Android的公共库类似,...
  • Android XML绘图之 Shape

    2017-12-05 11:56:36
    Android XML绘图之 Shape参考资料: 1.《Android群英传》 2.Keegan小钢博客地址一般用shape定义的xml文件存放在drawable目录下,若项目没有该目录则新建一个,而不要将它放到drawable-hdpi等目录中 通过Shape可以...
  • 比如出现下面这种情况,就是没有设置xml的代码风格,没有固定的代码格式去约束。...设置代码风格步骤:Settings—Editor—XML 选择右上角的Android风格,然后Apply即可。 然后再进行格式化效果如下图: ...
1 2 3 4 5 ... 20
收藏数 90,967
精华内容 36,386
关键字:

xml 新建android