Android应用界面开发(三)

一、Inflater与自定义控件

书接上回,我们现在来说一说Inflater。Inflater是将xml文件在Java文件中解析成视图的工具。它就相当于一个更大的findViewById,findVIewById是找xml文件中的控件,而Inflater则是找xml文件整个来做视图。

1.Inflater实例化的三种方式

工欲善其事必先利其器,无论我们使用什么工具,第一步都是将其实例化。

 mLayoutInflater = getLayoutInflater();
 mLayoutInflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE);
 mLayoutInflater = LayoutInflater.from(MainActivity.this);

是不是看起来很眼熟?对,在上一次我们再说listview的时候。就曾经使用过第二个方式来读取视图。那么,还记得我们在实例化之后怎么使用他的么?是的,我们使用了一个视图类型view使用Inflater方法得到了它。

View view = inflater.inflate(R.layout.custom, null);

在这之后,我们就可以使用view的名称.findViewById来找到控件进行操作了。如:

EditText editText = (EditText)view.findViewById(R.id.content);

2.如何自己画一个控件

自定义控件的方式是继承已有的控件、布局或直接继承view。这里以继承View为例

1/创建新的控件class并继承你想要继承的

public class MyLearn extends View{
    public MyLearn(Context context) {
        super(context);
    }

    public MyLearn(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public MyLearn(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

}

在继承后使用快捷键直接构成其构造器,然后在每个里面进行一次初始化。
当然,学过Java的小伙伴们都知道,使用this去调用其他的构造方法是一种重码率更低,更有效的方法。

    public TestRedButton(Context context) {this(context, null);}
    public TestRedButton(Context context, AttributeSet attrs) {this(context, attrs, 0);}
    public TestRedButton(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context,attrs, defStyleAttr);
        init(context,attrs);
    }

2/使用onDraw

    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        // 做一个圆形的红色按钮
        // 设置画布为红色

        /**
         * 画一个红色的圆
         */
        mPaint.setColor(mBackgroundColor);
        canvas.drawCircle(getWidth()/2,getHeight()/2,getWidth()/2, mPaint);
        //getWidth()与getHeight()是设置的属性数值,三个参数分别是位置和半径,最后一个是画布
        // 中间有一个白色的数字
        mPaint.setColor(Color.WHITE);
        mPaint.setTextSize(200);

        /**
         * mRect是这个数字四周的边距
         */
        String text = String.valueOf(mNumber);
        mPaint.getTextBounds(text, 0, text.length(), mRect);

        int textWidth = mRect.width();
        int textHeight = mRect.height();

        canvas.drawText(text, getWidth() / 2 - textWidth / 2, getHeight() / 2 + textHeight / 2, mPaint);
    }

另外由于onDraw方法将被多次使用,所以最好不要在onDraw方法中进行new操作。例如像我们这次写在init中

private void init(Context context, AttributeSet attrs) {
        mPaint = new Paint();//设置一个画布
        mRect = new Rect();
        }

扩展1/设置自定义控件的监听器

在init的方法中加入如下代码

this.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                    //你想要进行的一些操作
                    invalidate();  // 刷新界面
           }

在页面发生改变后一定要记得刷新界面。

扩展2/新增属性

(1)在value文件夹中创建新的attrs.xml文件(已有则无需创建)

(2)在attrs中写如下代码:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="TestRedButton">
        <attr name="backgroundColor" format="color"/>
        <attr name="textColor" format="color"/>
        <attr name="textSize"  format="dimension"/>
    </declare-styleable>
</resources>

eclare-styleable的name是相对应的控件的属性,其中attr的name是属性的名称,format是属性的类型。除了上述的两种以外还有很多的属性可以使用。

(3)在控件的Java文件中定义并使用

在init中加入如下代码,其中attrs由构造器提供。

        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.TestRedButton);
        //后面是eclare-styleable的name
        mBackgroundColor = typedArray.getColor(R.styleable.TestRedButton_backgroundColor, Color.BLUE);
        //eclare-styleable的名字加属性的名字和颜色
        mTextSize = typedArray.getDimensionPixelSize(R.styleable.TestRedButton_textSize, 18);
        //属性的名字和大小

二、Fragment的利用

1.Fragment的理解以及作用

Fragment是模板化的activity文件,是activity文件的一部分。众所周知,我们写代码写的好不好有一个很重要的标准,就是重码率。在很多场合中有很多的页面他们的布局有很大一部分是类似的。如果每一个activity都写一个对应的布局文件就会使得文件整体很大,有很多相同的代码。但是这两个activity相同的布局又只有一部分而不是两者之一的全部,没有办法使用不同数据的传输或者include。对于这种情况,Android使用了Fragment的方法,让我们可以将两个有部分相同布局的activity的相似部分抽出成为单独的一部分,分别在两个activity中进行利用,达到了降低重码率的效果。另外,Android运行在各种各样的设备中,有小屏幕的手机,超大屏的平板甚至电视。针对屏幕尺寸的差距,很多情况下,都是先针对手机开发一套App,然后拷贝一份,修改布局以适应平板神马超级大屏的。难道无法做到一个App可以同时适应手机和平板么,当然了,必须有啊。Fragment的出现就是为了解决这样的问题。你可以把Fragment当成Activity的一个界面的一个组成部分,甚至Activity的界面可以完全有不同的Fragment组成,更帅气的是Fragment拥有自己的生命周期和接收、处理用户的事件,这样就不必在Activity写一堆控件的事件处理的代码了。更为重要的是,你可以动态的添加、替换和移除某个Fragment。

2.Fragment的生命周期

Fragment的生命周期
Fragment必须是依存与Activity而存在的,因此Activity的生命周期会直接影响到Fragment的生命周期。从上图中我们可以看到比activity,Fragment多了几个方法:

  • onAttach(Activity):当Fragment与Activity发生关联时调用。
  • onCreateView(LayoutInflater, ViewGroup,Bundle):创建该Fragment的视图
  • onActivityCreated(Bundle):当Activity的onCreate方法返回时调用
  • onDestoryView():与onCreateView相对应,当该Fragment的视图被移除时调用

3.Fragment的xml加载

Fragment的静态用的其实很简单,把你想要调用的Fragment加入到xml文件中去即可。当然,你在Fragment的文件或者主文件中得有为它指定的视图才行。恩?不会?往上翻!
或者你直接这么干。干脆利落。

public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        return inflater.inflate(R.layout.item_phone_book_friend, container, false);
    }

顺便贴一下xml里的写法

    <fragment
        android:id="@+id/fragment_test"
        android:name="com.132.Test01.TestFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        />

name是你的Fragment类的名称

4.Fragment在Java中加载

1/首先我们都要有的两个对象

        FragmentManager fragmentManager = getFragmentManager();
        FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();

2/创建一个对象的实例

        TestFragment testFragment = TestFragment.newInstance("我是极客班",15);

3/将其添加到viewGroup

        fragmentTransaction.add(R.id.fragment_view, testFragment, "test_fragment_tag");

4/将其移除

 fragmentTransaction.remove(testFragment);

5/注意上述的操作都需要commit()之后才会生效

6/找到它并在对比后操作

        Fragment fragment = fragmentManager.findFragmentById(R.id.fragment_test);
        fragmentManager.addOnBackStackChangedListener(new FragmentManager.OnBackStackChangedListener() {
            @Override
            public void onBackStackChanged() {

            }
        });
        if(fragment instanceof TestFragment){
            // TODO: DO your action
        } else {
            throw new IllegalStateException("is not testFragment");
        }

7/常见的其他操作

  • transaction.replace():使用另一个Fragment替换当前的,实际上就是remove()然后add()的合体~
  • transaction.hide():隐藏当前的Fragment,仅仅是设为不可见,并不会销毁
  • transaction.show():显示之前隐藏的Fragment
  • detach()会将view从UI中移除,和remove()不同,此时fragment的状态依然由FragmentManager维护。
  • attach()重建view视图,附加到UI上并显示。
    注意:常用Fragment的哥们,可能会经常遇到这样Activity状态不一致:State loss这样的错误。主要是因为:commit方法一定要在Activity.onSaveInstance()之前调用。

附录:返回按钮点击方法

    @Override
    public void onBackPressed() {
        super.onBackPressed();
    }

附录二:更加详细的Fragment介绍博客

Android Fragment 真正的完全解析(上)Android Fragment 真正的完全解析(下)

三、Handler的多线程

1.handler的理解及为何应用

学过Java的小伙伴们都清楚,在Java中有一个东西它叫线程,它们负责与主线程同步处理各类事物。Android也是这样,handler则是负责主要接受子线程发送的数据, 并用此数据配合主线程更新UI。当应用程序启动时,Android首先会开启一个主线程 (也就是UI线程) , 主线程为管理界面中的UI控件, 进行事件分发,如果你在这个线程当中进行一个耗时很多的操作的话,就会造成UI的卡死,超过五秒之后Android就会报错,要求进行“强制关闭”。 这个时候我们需要把这些耗时的操作,放在一个子线程中,因为子线程涉及到UI更新,这将导致Android主线程不安全, 也就是说,更新UI只能在主线程中更新,子线程中操作是危险的。此时我们就需要在主线程与子线程之间使用handler进行通讯,把新的信息放入主线程之中进行UI的更新。

2.handler的使用

handler需要进行实例化后才能使用,一般我们不建议使用内部类进行handler的编写,因为会造成内存泄露。如果条件允许,请单独写一个外部类来进行handler的使用。

1/消息机制总结

1、实例化Looper(因为实例化Handler时需要一个looper);

2、实例化Handler,这里需要覆盖handleMessage方法,处理收到的消息;

3、 实例化Message对象,调用已经实例化好的handler对象的obtainMessage方法,把数据传给obtainMessage方法,obtainMessage方法就会实例化一个Message对象。(这里也可以发送实现Runnable接口的对象);

4、调用Handler的sendMessage方法把实例化好的Message对象发送出去。

2/handler类的写法

 class MyHandler extends Handler { 
        public MyHandler() { 
        } 

        public MyHandler(Looper L) { 
            super(L); 
        } 

        // 子类必须重写此方法,接受数据 
        @Override 
        public void handleMessage(Message msg) { 
            // TODO Auto-generated method stub  
            super.handleMessage(msg); 
            switch(msg.what)
                case ...:break;//这里利用what进行信息的判断
                case ...:break;
        } 
    } 

3.handler的一些方法

  • post(Runnable):运行一个线程
  • postAtTime(Runnable,long):在某时间运行一个线程
  • postDelayed(Runnable long):停滞多少微秒后运行一个线程
  • sendEmptyMessage(int):发送一个空消息
  • sendMessage(Message):发送一个消息
  • sendMessageAtTime(Message,long):在某时刻发送一个消息
  • sendMessageDelayed(Message,long):延迟多少秒后发送一个消息

4.关于looper

Android 系统的消息队列和消息循环都是针对具体线程的,一个线程可以存在(当然也可以不存在)一个消息队列和一个消息循环(Looper),特定线程的消息只能分发给本线程,不能进行跨线程,跨进程通讯。但是创建的工作线程默认是没有消息循环和消息队列的,如果想让该线程具有消息队列和消息循环,需要在线程中首先调用Looper.prepare()来创建消息队列,然后调用Looper.loop()进入消息循环。 更详细的looper分析由于没有授权,就不在这里写了。

5.一些好的handler博文

android Handler详细使用方法实例handler机制的原理

四、下期介绍

下一期,我们将介绍一下Android中的服务、广播等等。

猜你喜欢

转载自blog.csdn.net/qq_34939549/article/details/51490864
今日推荐