作者: duo, duo

android开发教程之使用线程实现视图平滑滚动示例

android开发教程之使用线程实现视图平滑滚动示例

*近一直想做下拉刷新的效果,琢磨了好久,才走到通过onTouch方法把整个视图往下拉的步骤,接下来就是能拉下来,松开手要能滑回去啊。网上看了好久,没有找到详细的下拉刷新的例子,只有自己慢慢琢磨了。昨天和今天,研究了两天,下拉之后回滚回去的效果终于今天做出来了!开心。现在来分享下我的实现方法和一些心得体会吧。
我看了网上一个大神的例子,发现是在onTouch里面使用View的scrollTo(int, int)方法,来使整个视图往下滚动的,我尝试了使用setTranslationY()来对视图进行回滚,*次是没问题的,但多滚动几次之后,整个视图实际上已经到了非常“高”的地方了,要拉很长的距离才能看到内容。所以回滚也必须使用scrollTo(int, int)方法来操作。
但scrollTo(int, int)执行是瞬间的,方法名讲是滚动到,实际上就是“瞬移到”某个位置,因此需要一步一步的去瞬移它,让它看上去是滚过去的……

因为等下要去跑步了,还有就是也没有什么很多的要点需要讲解,我就直接上代码给大家看,注释都写好了,要点会单独提一下,更详细的讲解与心得就等着我哪天把下拉刷新实现了吧。

复制代码 代码如下:
/**
* @desc    平滑滚动
* @param    v        需要操控的视图
* @param    fromY    起始Y坐标
* @param    toY        终止Y坐标
* @param    fps        帧率
* @param    durtion    动画完成时间(毫秒)
* */
private void smoothScroll(View v, int fromY, int toY, int fps, long durtion) {
smoothScrollThread = new SmoothScrollThread(v, fromY, toY, durtion, fps);
smoothScrollThread.run();
}

/**
* @desc    平滑滚动线程,用于递归调用自己来实现某个视图的平滑滚动
* */
class SmoothScrollThread implements Runnable {
//需要操控的视图
private View v = null;
//原Y坐标
private int fromY = 0;
//目标Y坐标
private int toY = 0;
//动画执行时间(毫秒)
private long durtion = 0;
//帧率
private int fps = 60;
//间隔时间(毫秒),间隔时间 = 1000 / 帧率
private int interval = 0;
//启动时间,-1 表示尚未启动
private long startTime = -1;
/减速插值器
private DecelerateInterpolator decelerateInterpolator = null;

/**
* @desc    构造方法,做好*次配置
* */
public SmoothScrollThread(View v, int fromY, int toY, long durtion, int fps) {
this.v = v;
this.fromY = fromY;
this.toY = toY;
this.durtion = durtion;
this.fps = fps;
this.interval = 1000 / this.fps;
decelerateInterpolator = new DecelerateInterpolator();
}
@Override
public void run() {
//先判断是否是*次启动,是*次启动就记录下启动的时间戳,该值仅此一次赋值
if (startTime == -1) {
startTime = System.currentTimeMillis();
}
//得到当前这个瞬间的时间戳
long currentTime = System.currentTimeMillis();
//放大倍数,为了扩大除法计算的浮点精度
int enlargement = 1000;
//算出当前这个瞬间运行到整个动画时间的百分之多少
float rate = (currentTime – startTime) * enlargement / durtion;
//这个比率不可能在 0 – 1 之间,放大了之后即是 0 – 1000 之间
rate = Math.min(rate, 1000);
//将动画的进度通过插值器得出响应的比率,乘以起始与目标坐标得出当前这个瞬间,视图应该滚动的距离。
int changeDistance = (int) ((fromY – toY) * decelerateInterpolator.getInterpolation(rate / enlargement));
int currentY = fromY – changeDistance;
v.scrollTo(0, currentY);
if (currentY != toY) {
postDelayed(this, this.interval);
} else {
return;
}
}
public void stop() {
removeCallbacks(this);
}
}

一些要点:

1.使用线程的目的是可以递归的调用自己,在每个run()方法里只滚动一点点,这个一点点根据帧率和插值器来决定。

2.插值器实际上就是一个函数(数学里的函数),输入0-1之间的浮点数,输出0-1之间的浮点数,输出的曲线是什么样的,就看是什么插值器了,decelerate就是减速插值器了,在平面直角坐标系里面,x值均匀变化,y轴的变化越来越慢。

3.放大倍数(就是那里乘以1000)是为了提高精度,因为通过实践发现用已经过的毫秒数除以整个动画周期得出的结果是0.0 -> 0.0 -> 0.0 -> 0.0 -> 1.0 -> 1.0 -> 1.0 -> 1.0 -> 1.0 -> 2.0 -> 2.0 -> 2.0,虽然是浮点数,但精度却莫名的保持在个位数上,乘以1000后,便会出现0-1000的均匀变化,这个时候去除以1000,便可得到0.000 – 1.000之间的均匀变化的数。

4.还有个很奇葩的是MotionEvent.getY()的值和scrollTo(int,int)的值貌似不是在同一个坐标系里面的。这个还有待进一步的分析和研究啊。

 

android开发教程之使用线程实现视图平滑滚动示例 改

复制代码
package com.melonsapp.messenger.ui.popupuser;

import android.os.Handler;
import android.view.View;
import android.view.WindowManager;
import android.view.animation.DecelerateInterpolator;

import java.util.Timer;

/**
 * Created by lidaqiang on 17/5/3.
 */

public class SmoothScroll {
    private Handler mHandler = new Handler();
    SmoothScrollThread smoothScrollThread;
    public static int noData = 0;

    /**
     * @param v       需要操控的视图
     * @param fromX   起始Y坐标
     * @param toX     终止Y坐标
     * @param fps     帧率
     * @param durtion 动画完成时间(毫秒)
     * @desc 平滑滚动
     */
    public SmoothScroll(View v, WindowManager windowManager, WindowManager.LayoutParams windowParams, int fromX, int toX, int fps, long durtion) {
        this(v, windowManager, windowParams, fromX, toX, noData, noData, 60, durtion);
    }

    public SmoothScroll(View v, WindowManager windowManager, WindowManager.LayoutParams windowParams, int fromX, int toX, int fromY, int toY, long durtion) {
        this(v, windowManager, windowParams, fromX, toX, fromY, toY, 60, durtion);
    }

    public SmoothScroll(View v, WindowManager windowManager, WindowManager.LayoutParams windowParams, int fromX, int toX, int fromY, int toY, int fps, long durtion) {
        smoothScrollThread = new SmoothScrollThread(v, windowManager, windowParams, fromX, toX, fromY, toY, durtion, fps);
    }

    public void start() {
        if (smoothScrollThread == null) {
            return;
        }
        smoothScrollThread.run();
    }

    public void stop() {
        if (smoothScrollThread == null) {
            return;
        }
        smoothScrollThread.stop();
    }

    /**
     * @desc 平滑滚动线程,用于递归调用自己来实现某个视图的平滑滚动
     */
    class SmoothScrollThread implements Runnable {
        WindowManager mWindowManager;
        WindowManager.LayoutParams mWindowParams;
        //需要操控的视图
        private View v = null;
        //原X坐标
        private int fromX = noData;
        //目标X坐标
        private int toX = noData;

        //原Y坐标
        private int fromY = noData;
        //目标Y坐标
        private int toY = noData;
        //动画执行时间(毫秒)
        private long durtion = 0;
        //帧率
        private int fps = 60;
        //间隔时间(毫秒),间隔时间 = 1000 / 帧率
        private int interval = 0;
        //启动时间,-1 表示尚未启动
        private long startTime = -1;
        //        /减速插值器
        private DecelerateInterpolator decelerateInterpolator = null;

        private int mChangeState = 0;  // 0 x,y都不变   1 x变      2 y变   3 x,y都变

        /**
         * @desc 构造方法,做好*次配置
         */
        public SmoothScrollThread(View v, WindowManager windowManager, WindowManager.LayoutParams windowParams, int fromX, int toX, int fromY, int toY, long durtion, int fps) {
            mWindowManager = windowManager;
            mWindowParams = windowParams;
            this.v = v;
            this.fromX = fromX;
            this.toX = toX;
            this.fromY = fromY;
            this.toY = toY;
            this.durtion = durtion;
            this.fps = fps;
            this.interval = 1000 / this.fps;
            decelerateInterpolator = new DecelerateInterpolator();
            mChangeState = 0;

            if (fromX != toX && fromY == toY) {
                mChangeState = 1;
            } else if (fromX == toX && fromY != toY) {
                mChangeState = 2;
            } else if (fromX != toX && fromY != toY) {
                mChangeState = 3;
            }
        }

        @Override
        public void run() {

            if (mChangeState == 0) {
                return;
            }

            //先判断是否是*次启动,是*次启动就记录下启动的时间戳,该值仅此一次赋值
            if (startTime == -1) {
                startTime = System.currentTimeMillis();
            }
            //得到当前这个瞬间的时间戳
            long currentTime = System.currentTimeMillis();
            //放大倍数,为了扩大除法计算的浮点精度
            int enlargement = 1000;
            //算出当前这个瞬间运行到整个动画时间的百分之多少
            float rate = (currentTime - startTime) * enlargement / durtion;
            //这个比率不可能在 0 - 1 之间,放大了之后即是 0 - 1000 之间
            rate = Math.min(rate, 1000);
            //将动画的进度通过插值器得出响应的比率,乘以起始与目标坐标得出当前这个瞬间,视图应该滚动的距离。

            int currentX = fromX;
            if (mChangeState == 1 || mChangeState == 3) {
                int changeDistanceX = (int) ((fromX - toX) * decelerateInterpolator.getInterpolation(rate / enlargement));
                currentX = fromX - changeDistanceX;
            }

            int currentY = fromY;
            if (mChangeState == 2 || mChangeState == 3) {
                int changeDistanceY = (int) ((fromY - toY) * decelerateInterpolator.getInterpolation(rate / enlargement));
                currentY = fromY - changeDistanceY;
            }


            notifyViewLayout(currentX, currentY);

            if (currentX != toX || currentY != toY) {


                mHandler.postDelayed(this, this.interval);
            } else {
                return;
            }
        }


        private void notifyViewLayout(int currentX, int currentY) {
//            v.scrollTo(0, currentY);
            if (mWindowParams == null || mWindowParams == null || v == null) {
                return;
            }

            if (mChangeState == 1 || mChangeState == 3) {
                mWindowParams.x = currentX;
            }


            if (mChangeState == 2 || mChangeState == 3) {
                mWindowParams.y = currentY;
            }


            if (v.getParent() != null) {
                mWindowManager.updateViewLayout(v, mWindowParams);
            }

        }

        public void stop() {
            mHandler.removeCallbacks(this);
        }
    }


}
复制代码

 

Android 屏幕适配(二)增强版百分比布局库(percent-support-lib)

Android 屏幕适配(二)增强版百分比布局库(percent-support-lib)

一 概述

上周一我们发布了Android 百分比布局库(percent-support-lib) 解析与扩展中对percent-support这个库进行了解析和添加了PercentLinearLayout的支持。

那么为什么本篇博客的存在的意义是什么呢?

首先我们回顾下百分比布局库的用法,提供了PercentRelativeLayoutPercentFrameLayout供大家在编写的时候,对于以下属性:

layout_widthPercentlayout_heightPercent
layout_marginPercentlayout_marginLeftPercent
layout_marginTopPercentlayout_marginRightPercent
layout_marginBottomPercentlayout_marginStartPercentlayout_marginEndPercent

可以使用百分比进行设置宽、高、边距,的确给我们在适配上提供了*大的便利,但是在使用过程中,觉得存在一些场景无法得到满足。什么场景呢?下面我举几个例子。

  1. 当使用图片时,无法设置宽高的比例

    比如我们的图片宽高是200*100的,我们在使用过程中我们设置宽高为20%、10%,这样会造成图片的比例失调。为什么呢?因为20%参考的是屏幕的宽度,而10%参考的是屏幕的高度。

  2. 很难使用百分比定义一个正方形的控件

    比如,我现在界面的右下角有一个FloatingActionButton,我希望其宽度和高度都为屏幕宽度的10%,很难做到。

  3. 一个控件的margin四个方向值一致

    有些时候,我设置margin,我希望四边的边距一致的,但是如果目前设置5%,会造成,上下为高度的5%,左右边距为宽度的5%。

综合上述这些问题,可以发现目前的percent-support-lib并不能完全满足我们的需求,所以我们考虑对其进行扩展。说白了,我们就希 望在布局的时候可以自己设定参考看度还是高度,比如上述2,我们对于宽高可以写成10%w,10%w。也就是在不改变原库的用法的前提下,添加一些额外的 支持。


二 扩展的功能

目前我初步对该库进行了改写,github地址:android-percent-support-extend,对于官方库,做了如下的改变:

  1. 不改变原有库的用法
  2. 添加了PercentLinearLayout
  3. 支持百分比指定特定的参考值,比如宽度或者高度。

    例如:app:layout_heightPercent="50%w"app:layout_marginPercent="15%w",
    app:layout_marginBottomPercent="20%h".

  4. 支持通过app:layout_textSizePercent设置textView的textSize
  5. 对于外层套ScrollView的问题,目前可以在PercentLinearLayout的外层使用ScrollView,不过对于宽度的百分比参考的就是android.R.id.content的高度(因为,无法参考父控件的高度,父控件的高度理论上依赖于子View高度,且模式为UNSPECIFIED)。

对于如何导入,也是相当的简单,android studio的用户,直接:

dependencies {
    //...
    compile 'com.zhy:percent-support-extends:1.0.1'
}

不需要导入官方的percent-support-lib了。

对于的三个类分别为:

com.zhy.android.percent.support.PercentLinearLayout
com.zhy.android.percent.support.PercentRelativeLayout
com.zhy.android.percent.support.PercentFrameLayout

对于eclipse的用户:github上自行下载源码,就几个类和一个attrs.xml,也可以在bintray.com/percent-support-extends 下载相关文件。

下面看几个具体的示例。


三 具体的示例

Demo 1

%title插图%num

xml:

<?xml version="1.0" encoding="utf-8"?>


<com.zhy.android.percent.support.PercentFrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <com.zhy.android.percent.support.PercentFrameLayout
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_gravity="center"
        android:background="#ff44aacc"
        app:layout_heightPercent="50%w"
        app:layout_widthPercent="50%w">

        <com.zhy.android.percent.support.PercentFrameLayout
            android:layout_width="0dp"
            android:layout_height="0dp"
            android:layout_gravity="center"
            android:background="#ffcc5ec7"
            app:layout_heightPercent="50%w"
            app:layout_widthPercent="50%w">

            <TextView
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:layout_gravity="center"
                android:background="#ff7ecc16"
                android:gravity="center"
                android:text="margin 15% of w"
                app:layout_marginPercent="15%w"
                />

        </com.zhy.android.percent.support.PercentFrameLayout>

    </com.zhy.android.percent.support.PercentFrameLayout>

    <TextView android:layout_width="0dp"
              android:layout_height="0dp"
              android:layout_gravity="bottom|right"
              android:background="#44ff0000"
              android:gravity="center"
              android:text="15%w,15%w"
              app:layout_heightPercent="15%w"
              app:layout_marginPercent="5%w"
              app:layout_widthPercent="15%w"/>


</com.zhy.android.percent.support.PercentFrameLayout>

Demo 2

%title插图%num

xml:

<?xml version="1.0" encoding="utf-8"?>
<com.zhy.android.percent.support.PercentRelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:clickable="true">

    <TextView
        android:id="@+id/row_one_item_one"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_alignParentTop="true"
        android:background="#7700ff00"
        android:text="w:70%,h:20%"
        android:gravity="center"
        app:layout_heightPercent="20%"
        app:layout_widthPercent="70%"/>

    <TextView
        android:id="@+id/row_one_item_two"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_toRightOf="@+id/row_one_item_one"
        android:background="#396190"
        android:text="w:30%,h:20%"
        app:layout_heightPercent="20%"
        android:gravity="center"
        app:layout_widthPercent="30%"/>


    <ImageView
        android:id="@+id/row_two_item_one"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:src="@drawable/tangyan"
        android:scaleType="centerCrop"
        android:layout_below="@+id/row_one_item_one"
        android:background="#d89695"
        app:layout_heightPercent="70%"/>

    <TextView
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_below="@id/row_two_item_one"
        android:background="#770000ff"
        android:gravity="center"
        android:text="width:100%,height:10%"
        app:layout_heightPercent="10%"
        app:layout_widthPercent="100%"/>


</com.zhy.android.percent.support.PercentRelativeLayout>

ok,例子都比较简单,主要就一个布局文件,可以看出上述我们可以给宽度、高度,边距等指定参考值为宽度或者高度。这样的话,在保证图片宽、高比例、控件设置为正方形等需求就没问题了。


接下来还有个例子,功能主要是设置TextView对于textSize的百分比设置;以及对于ScrollView的支持。当然了,对于ScrollView的支持,这个理论上是不支持的,因为大家都清楚,如果PercentLinearLayout在ScrollView中,那么高度的模式肯定是UNSPECIFIED, 那么理论上来说高度是无限制的,也就是依赖于子View的高度,而百分比布局的高度是依赖于父View的高度的,所有是互斥的。而我们支持是:考虑到编写 代码的时候,大多参考的是屏幕高度(android.R.id.content)的高度,所以如果在ScrollView中,编写10%h,这个百分比是 依赖于屏幕高度的(不包括ActionBar的高度)。

Demo 3

%title插图%num

xml:

<?xml version="1.0" encoding="utf-8"?>

<ScrollView
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <com.zhy.android.percent.support.PercentLinearLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">

        <TextView
            android:layout_width="0dp"
            android:layout_height="0dp"
            android:background="#ff44aacc"
            android:gravity="center"
            android:text="width:60%,height:5%,ts:3%"
            android:textColor="#ffffff"
            app:layout_heightPercent="5%"
            app:layout_marginBottomPercent="5%"
            app:layout_textSizePercent="3%"
            app:layout_widthPercent="60%"/>

        <TextView
            android:layout_width="0dp"
            android:layout_height="0dp"
            android:background="#ff4400cc"
            android:gravity="center"
            android:text="width:70%,height:10%"
            android:textColor="#ffffff"
            app:layout_heightPercent="10%"
            app:layout_marginBottomPercent="5%"
            app:layout_widthPercent="70%"/>
        <TextView
            android:layout_width="0dp"
            android:layout_height="0dp"
            android:background="#ff44aacc"
            android:gravity="center"
            android:text="w:80%,h:15%,textSize:5%"
            android:textColor="#ffffff"
            app:layout_heightPercent="15%"
            app:layout_marginBottomPercent="5%"
            app:layout_textSizePercent="5%"
            app:layout_widthPercent="80%"/>
        <TextView
            android:layout_width="0dp"
            android:layout_height="0dp"
            android:background="#ff4400cc"
            android:gravity="center"
            android:text="width:90%,height:5%"
            android:textColor="#ffffff"
            app:layout_heightPercent="20%"
            app:layout_marginBottomPercent="5%"
            app:layout_widthPercent="90%"/>

        <TextView
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:background="#ff44aacc"
            android:gravity="center"
            android:text="width:100%,height:25%"
            android:textColor="#ffffff"
            app:layout_heightPercent="25%"
            app:layout_marginBottomPercent="5%"
            />

        <TextView
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:background="#ff44aacc"
            android:gravity="center"
            android:text="width:100%,height:30%"
            android:textColor="#ffffff"
            app:layout_heightPercent="30%"
            app:layout_marginBottomPercent="5%"
            />


    </com.zhy.android.percent.support.PercentLinearLayout>
</ScrollView>

上面的第三个TextView的字体设置的就是5%(默认参考容器高度)。整个PercentLinearLayout在ScrollView中。ok~ 姑且这样,由于源码比较简单,大家可以根据自己的实际需求去修改,前提尽可能不要改变原有的功能。


四 扩展的相关源码

(一) 关于attrs.xml

原库中所有的属性的format为fraction,但是由于我期望的写法有10%w,10%h,10%,没有找到合适的format,就直接定义为string了~string我可以自己去解析~

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="PercentLayout_Layout">
        <attr name="layout_widthPercent" format="string"/>
        <attr name="layout_heightPercent" format="string"/>
        <attr name="layout_marginPercent" format="string"/>
        <attr name="layout_marginLeftPercent" format="string"/>
        <attr name="layout_marginTopPercent" format="string"/>
        <attr name="layout_marginRightPercent" format="string"/>
        <attr name="layout_marginBottomPercent" format="string"/>
        <attr name="layout_marginStartPercent" format="string"/>
        <attr name="layout_marginEndPercent" format="string"/>
        <attr name="layout_textSizePercent" format="string"/>
    </declare-styleable>
</resources>

(二) 获取自定义属性的值及使用

如果看了上篇博文的话,应该清楚,对于自定义属性的值是在PercentLayoutHelper.getPercentLayoutInfo(c, attrs)中获取的。
简单看下修改后的代码:


    public static PercentLayoutInfo getPercentLayoutInfo(Context context,                                                    AttributeSet attrs)
    {
        PercentLayoutInfo info = null;
        TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.PercentLayout_Layout);

        String sizeStr = array.getString(R.styleable.PercentLayout_Layout_layout_widthPercent);
        PercentLayoutInfo.PercentVal percentVal = getPercentVal(sizeStr, true);
        if (percentVal != null)
        {
            if (Log.isLoggable(TAG, Log.VERBOSE))
            {
                Log.v(TAG, "percent width: " + percentVal.percent);
            }
            info = info != null ? info : new PercentLayoutInfo();
            info.widthPercent = percentVal;
        } 

        //省略了获取其他的类似属性
        array.recycle();
        return info;
    }


    private static final String REGEX_PERCENT = "^(([0-9]+)([.]([0-9]+))?|([.]([0-9]+))?)%([wh]?)$";

    /**
     * widthStr to PercentVal
     * <br/>
     * eg: 35%w => new PercentVal(35, true)
     *
     * @param percentStr
     * @param isOnWidth
     * @return
     */
    private static PercentLayoutInfo.PercentVal getPercentVal(String percentStr, boolean isOnWidth)
    {
        //valid param
        if (percentStr == null)
        {
            return null;
        }
        Pattern p = Pattern.compile(REGEX_PERCENT);
        Matcher matcher = p.matcher(percentStr);
        if (!matcher.matches())
        {
            throw new RuntimeException("the value of layout_xxxPercent invalid! ==>" + percentStr);
        }
        int len = percentStr.length();
        //extract the float value
        String floatVal = matcher.group(1);
        String lastAlpha = percentStr.substring(len - 1);

        float percent = Float.parseFloat(floatVal) / 100f;
        boolean isBasedWidth = (isOnWidth && !lastAlpha.equals("h")) || lastAlpha.equals("w");

        return new PercentLayoutInfo.PercentVal(percent, isBasedWidth);
    }

首先我们获取自定义属性的填写的值,通过getPercentVal方法,在该方法内部通过正则校验其合法性,如果合法,则将其拆解封装成 PercentVal对象,该对象中记录百分比值,已经知否参考宽度的布尔值(如果参考宽度则为true,否则为false)。对于没有后缀w|h的,和 原库的解析方式相同。

PercentVal对象如下:

public static class PercentVal
{

     public float percent = -1;
     public boolean isBaseWidth;

     public PercentVal(float percent, boolean isBaseWidth)
     {
          this.percent = percent;
          this.isBaseWidth = isBaseWidth;
     }
}       

对于定义的自定义属性获取完成之后,剩下的无非是测量时候对于原本的LayoutParams中的宽度和高度的赋值做简单的修改。参考上一篇的源码,我们直接看 PercentLayoutInfo.fillLayoutParams(params, widthHint, heightHint);方法:


 public void fillLayoutParams(ViewGroup.LayoutParams params, int widthHint,
                                     int heightHint)
        {
            // Preserve the original layout params, so we can restore them after the measure step.
            mPreservedParams.width = params.width;
            mPreservedParams.height = params.height;
            /*
            if (widthPercent >= 0) {
                params.width = (int) (widthHint * widthPercent);
            }
            if (heightPercent >= 0) {
                params.height = (int) (heightHint * heightPercent);
            }*/
            if (widthPercent != null)
            {
                int base = widthPercent.isBaseWidth ? widthHint : heightHint;
                params.width = (int) (base * widthPercent.percent);
            }
            if (heightPercent != null)
            {
                int base = heightPercent.isBaseWidth ? widthHint : heightHint;
                params.height = (int) (base * heightPercent.percent);
            }

            if (Log.isLoggable(TAG, Log.DEBUG))
            {
                Log.d(TAG, "after fillLayoutParams: (" + params.width + ", " + params.height + ")");
            }
        }

原本的源码比较简单,只需要将widthHint/heightHint乘以百分比即可(见上代码注释),而我们修改的也比较容易,首先判断参考宽度还是高度,然后乘以百分比(根据我们的对象PercentVal的属性)。

ok,大概的源码修改就是上述的内容,有兴趣的可以直接查看源码。

当然了,上述库中肯定还存在或多或少的问题,大家可以fork完善下,或者直接留言提意见都可以。

Android 屏幕适配(一)百分比布局库(percent-support-lib) 解析与扩展

一、概述

发现android-percent-support-lib-sample这个项目,Google终于开始支持百分比的方式布局了,瞬间脉动回来,啊咧咧。对于这种历史性的时刻,不出篇文章难以表达我内心的激动。

还记得不久前,发了篇文:Android 屏幕适配方案,这篇文章以Web页面设计引出一种适配方案,*终的目的就是可以通过百分比控制控件的大小。当然了,存在一些问题,比如:

  • 对于没有考虑到屏幕尺寸,可能会出现意外的情况;
  • apk的大小会增加;

当然了android-percent-support这个库,基本可以解决上述问题,是不是有点小激动,稍等,我们先描述下这个support-lib。

这个库提供了:

  • 两种布局供大家使用:
    PercentRelativeLayoutPercentFrameLayout,通过名字就可以看出,这是继承自FrameLayoutRelativeLayout两个容器类;
  • 支持的属性有:

layout_widthPercentlayout_heightPercent
layout_marginPercentlayout_marginLeftPercent
layout_marginTopPercentlayout_marginRightPercent
layout_marginBottomPercentlayout_marginStartPercentlayout_marginEndPercent

可以看到支持宽高,以及margin。

也就是说,大家只要在开发过程中使用PercentRelativeLayoutPercentFrameLayout替换FrameLayoutRelativeLayout即可。

是不是很简单,不过貌似没有LinearLayout,有人会说LinearLayout有weight属性呀。但是,weight属性只能支持一个方向呀~~哈,没事,刚好给我们一个机会去自定义一个PercentLinearLayout

好了,本文分为3个部分:

  • PercentRelativeLayoutPercentFrameLayout的使用
  • 对上述控件源码分析
  • 自定义PercentLinearLayout

二、使用

关于使用,其实及其简单,并且github上也有例子,android-percent-support-lib-sample。我们就简单过一下:

首先记得在build.gradle添加:

 compile 'com.android.support:percent:22.2.0'

(一)PercentFrameLayout

<?xml version="1.0" encoding="utf-8"?>
<android.support.percent.PercentFrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_gravity="left|top"
        android:background="#44ff0000"
        android:text="width:30%,height:20%"
        app:layout_heightPercent="20%"
        android:gravity="center"
        app:layout_widthPercent="30%"/>

    <TextView
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_gravity="right|top"
        android:gravity="center"
        android:background="#4400ff00"
        android:text="width:70%,height:20%"
        app:layout_heightPercent="20%"
        app:layout_widthPercent="70%"/>


    <TextView
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_gravity="bottom"
        android:background="#770000ff"
        android:text="width:100%,height:10%"
        android:gravity="center"
        app:layout_heightPercent="10%"
        app:layout_widthPercent="100%"/>


</android.support.percent.PercentFrameLayout>

3个TextView,很简单,直接看效果图:

%title插图%num


(二) PercentRelativeLayout

<?xml version="1.0" encoding="utf-8"?>
<android.support.percent.PercentRelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:clickable="true">

    <TextView
        android:id="@+id/row_one_item_one"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_alignParentTop="true"
        android:background="#7700ff00"
        android:text="w:70%,h:20%"
        android:gravity="center"
        app:layout_heightPercent="20%"
        app:layout_widthPercent="70%"/>

    <TextView
        android:id="@+id/row_one_item_two"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_toRightOf="@+id/row_one_item_one"
        android:background="#396190"
        android:text="w:30%,h:20%"
        app:layout_heightPercent="20%"
        android:gravity="center"
        app:layout_widthPercent="30%"/>


    <ImageView
        android:id="@+id/row_two_item_one"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:src="@drawable/tangyan"
        android:scaleType="centerCrop"
        android:layout_below="@+id/row_one_item_one"
        android:background="#d89695"
        app:layout_heightPercent="70%"/>

    <TextView
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_below="@id/row_two_item_one"
        android:background="#770000ff"
        android:gravity="center"
        android:text="width:100%,height:10%"
        app:layout_heightPercent="10%"
        app:layout_widthPercent="100%"/>


</android.support.percent.PercentRelativeLayout>

ok,依然是直接看效果图:

%title插图%num

使用没什么好说的,就是直观的看一下。


三、源码分析

其实细想一下,Google只是对我们原本熟悉的RelativeLayout和FrameLayout进行的功能的扩展,使其支持了percent相关的属性。

那么,我们考虑下,如果是我们添加这种扩展,我们会怎么做:

  • 通过LayoutParams获取child设置的percent相关属性的值
  • onMeasure的时候,将child的width,height的值,通过获取的自定义属性的值进行计算(eg:容器的宽 * fraction ),计算后传入给child.measure(w,h);

ok,有了上面的猜想,我们直接看PercentFrameLayout的源码。

public class PercentFrameLayout extends FrameLayout {
    private final PercentLayoutHelper mHelper = new PercentLayoutHelper(this);

    //省略了,两个构造方法

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



    @Override
    public LayoutParams generateLayoutParams(AttributeSet attrs) {
        return new LayoutParams(getContext(), attrs);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        mHelper.adjustChildren(widthMeasureSpec, heightMeasureSpec);
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        if (mHelper.handleMeasuredStateTooSmall()) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        }
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        mHelper.restoreOriginalParams();
    }

    public static class LayoutParams extends FrameLayout.LayoutParams
            implements PercentLayoutHelper.PercentLayoutParams {
        private PercentLayoutHelper.PercentLayoutInfo mPercentLayoutInfo;

        public LayoutParams(Context c, AttributeSet attrs) {
            super(c, attrs);
            mPercentLayoutInfo = PercentLayoutHelper.getPercentLayoutInfo(c, attrs);
        }
        //省略了一些代码...

        @Override
        public PercentLayoutHelper.PercentLayoutInfo getPercentLayoutInfo() {
            return mPercentLayoutInfo;
        }

        @Override
        protected void setBaseAttributes(TypedArray a, int widthAttr, int heightAttr) {
            PercentLayoutHelper.fetchWidthAndHeight(this, a, widthAttr, heightAttr);
        }
    }
}

代码是相当的短,可以看到PercentFrameLayout里面首先重写了generateLayoutParams方法,当然了,由于支持了一些新的layout_属性,那么肯定需要定义对应的LayoutParams。


(一)percent相关属性的获取

可以看到PercentFrameLayout.LayoutParams在原有的FrameLayout.LayoutParams基础上,实现了PercentLayoutHelper.PercentLayoutParams接口。

这个接口很简单,只有一个方法:

public interface PercentLayoutParams {
        PercentLayoutInfo getPercentLayoutInfo();
    }

而,这个方法的实现呢,也只有一行:return mPercentLayoutInfo;,那么这个mPercentLayoutInfo在哪完成赋值呢?

看PercentFrameLayout.LayoutParams的构造方法:

public LayoutParams(Context c, AttributeSet attrs) {
            super(c, attrs);
            mPercentLayoutInfo = PercentLayoutHelper.getPercentLayoutInfo(c, attrs);
        }

可以看到,将attrs传入给getPercentLayoutInfo方法,那么不用说,这个方法的内部,肯定是获取自定义属性的值,然后将其封装到PercentLayoutInfo对象中,*后返回。

代码如下:

public static PercentLayoutInfo getPercentLayoutInfo(Context context,
            AttributeSet attrs) {
        PercentLayoutInfo info = null;
        TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.PercentLayout_Layout);
        float value = array.getFraction(R.styleable.PercentLayout_Layout_layout_widthPercent, 1, 1,
                -1f);
        if (value != -1f) {
            if (Log.isLoggable(TAG, Log.VERBOSE)) {
                Log.v(TAG, "percent width: " + value);
            }
            info = info != null ? info : new PercentLayoutInfo();
            info.widthPercent = value;
        }
        value = array.getFraction(R.styleable.PercentLayout_Layout_layout_heightPercent, 1, 1, -1f);
        if (value != -1f) {
            if (Log.isLoggable(TAG, Log.VERBOSE)) {
                Log.v(TAG, "percent height: " + value);
            }
            info = info != null ? info : new PercentLayoutInfo();
            info.heightPercent = value;
        }
        value = array.getFraction(R.styleable.PercentLayout_Layout_layout_marginPercent, 1, 1, -1f);
        if (value != -1f) {
            if (Log.isLoggable(TAG, Log.VERBOSE)) {
                Log.v(TAG, "percent margin: " + value);
            }
            info = info != null ? info : new PercentLayoutInfo();
            info.leftMarginPercent = value;
            info.topMarginPercent = value;
            info.rightMarginPercent = value;
            info.bottomMarginPercent = value;
        }
        value = array.getFraction(R.styleable.PercentLayout_Layout_layout_marginLeftPercent, 1, 1,
                -1f);
        if (value != -1f) {
            if (Log.isLoggable(TAG, Log.VERBOSE)) {
                Log.v(TAG, "percent left margin: " + value);
            }
            info = info != null ? info : new PercentLayoutInfo();
            info.leftMarginPercent = value;
        }
        value = array.getFraction(R.styleable.PercentLayout_Layout_layout_marginTopPercent, 1, 1,
                -1f);
        if (value != -1f) {
            if (Log.isLoggable(TAG, Log.VERBOSE)) {
                Log.v(TAG, "percent top margin: " + value);
            }
            info = info != null ? info : new PercentLayoutInfo();
            info.topMarginPercent = value;
        }
        value = array.getFraction(R.styleable.PercentLayout_Layout_layout_marginRightPercent, 1, 1,
                -1f);
        if (value != -1f) {
            if (Log.isLoggable(TAG, Log.VERBOSE)) {
                Log.v(TAG, "percent right margin: " + value);
            }
            info = info != null ? info : new PercentLayoutInfo();
            info.rightMarginPercent = value;
        }
        value = array.getFraction(R.styleable.PercentLayout_Layout_layout_marginBottomPercent, 1, 1,
                -1f);
        if (value != -1f) {
            if (Log.isLoggable(TAG, Log.VERBOSE)) {
                Log.v(TAG, "percent bottom margin: " + value);
            }
            info = info != null ? info : new PercentLayoutInfo();
            info.bottomMarginPercent = value;
        }
        value = array.getFraction(R.styleable.PercentLayout_Layout_layout_marginStartPercent, 1, 1,
                -1f);
        if (value != -1f) {
            if (Log.isLoggable(TAG, Log.VERBOSE)) {
                Log.v(TAG, "percent start margin: " + value);
            }
            info = info != null ? info : new PercentLayoutInfo();
            info.startMarginPercent = value;
        }
        value = array.getFraction(R.styleable.PercentLayout_Layout_layout_marginEndPercent, 1, 1,
                -1f);
        if (value != -1f) {
            if (Log.isLoggable(TAG, Log.VERBOSE)) {
                Log.v(TAG, "percent end margin: " + value);
            }
            info = info != null ? info : new PercentLayoutInfo();
            info.endMarginPercent = value;
        }
        array.recycle();
        if (Log.isLoggable(TAG, Log.DEBUG)) {
            Log.d(TAG, "constructed: " + info);
        }
        return info;
    }

是不是和我们平时的取值很类似,所有的值*终封装到PercentLayoutInfo对象中。

ok,到此我们的属性获取就介绍完成,有了这些属性,是不是onMeasure里面要进行使用呢?


(二) onMeasue中重新计算child的尺寸

@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        mHelper.adjustChildren(widthMeasureSpec, heightMeasureSpec);
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        if (mHelper.handleMeasuredStateTooSmall()) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        }
    }

可以看到onMeasure中的代码页很少,看来核心的代码都被封装在mHelper的方法中,我们直接看mHelper.adjustChildren方法。

/**
     * Iterates over children and changes their width and height to one calculated from percentage
     * values.
     * @param widthMeasureSpec Width MeasureSpec of the parent ViewGroup.
     * @param heightMeasureSpec Height MeasureSpec of the parent ViewGroup.
     */
    public void adjustChildren(int widthMeasureSpec, int heightMeasureSpec) {
        //...
        int widthHint = View.MeasureSpec.getSize(widthMeasureSpec);
        int heightHint = View.MeasureSpec.getSize(heightMeasureSpec);
        for (int i = 0, N = mHost.getChildCount(); i < N; i++) {
            View view = mHost.getChildAt(i);
            ViewGroup.LayoutParams params = view.getLayoutParams();

            if (params instanceof PercentLayoutParams) {
                PercentLayoutInfo info =
                        ((PercentLayoutParams) params).getPercentLayoutInfo();
                if (Log.isLoggable(TAG, Log.DEBUG)) {
                    Log.d(TAG, "using " + info);
                }
                if (info != null) {
                    if (params instanceof ViewGroup.MarginLayoutParams) {
                        info.fillMarginLayoutParams((ViewGroup.MarginLayoutParams) params,
                                widthHint, heightHint);
                    } else {
                        info.fillLayoutParams(params, widthHint, heightHint);
                    }
                }
            }
        }
    }

通过注释也能看出,此方法中遍历所有的孩子,通过百分比的属性重新设置其宽度和高度。

首先在widthHint、heightHint保存容器的宽、高,然后遍历所有的孩子,判断其LayoutParams是否是PercentLayoutParams类型,如果是,通过params.getPercentLayoutInfo拿出info对象。

是否还记得,上面的分析中,PercentLayoutInfo保存了percent相关属性的值。

如果info不为null,则判断是否需要处理margin;我们直接看fillLayoutParams方法(处理margin也是类似的)。

 /**
         * Fills {@code ViewGroup.LayoutParams} dimensions based on percentage values.
         */
        public void fillLayoutParams(ViewGroup.LayoutParams params, int widthHint,
                int heightHint) {
            // Preserve the original layout params, so we can restore them after the measure step.
            mPreservedParams.width = params.width;
            mPreservedParams.height = params.height;

            if (widthPercent >= 0) {
                params.width = (int) (widthHint * widthPercent);
            }
            if (heightPercent >= 0) {
                params.height = (int) (heightHint * heightPercent);
            }
            if (Log.isLoggable(TAG, Log.DEBUG)) {
                Log.d(TAG, "after fillLayoutParams: (" + params.width + ", " + params.height + ")");
            }
        }

首先保存原本的width和height,然后重置params的width和height为(int) (widthHint * widthPercent)(int) (heightHint * heightPercent);

到此,其实我们的百分比转换就结束了,理论上就已经实现了对于百分比的支持,不过Google还考虑了一些细节。

我们回到onMeasure方法:

@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        mHelper.adjustChildren(widthMeasureSpec, heightMeasureSpec);
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        if (mHelper.handleMeasuredStateTooSmall()) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        }
    }

下面还有个mHelper.handleMeasuredStateTooSmall的判断,也就是说,如果你设置的百分比,*终计算出来的MeasuredSize过小的话,会进行一些操作。
代码如下:

public boolean handleMeasuredStateTooSmall() {
        boolean needsSecondMeasure = false;
        for (int i = 0, N = mHost.getChildCount(); i < N; i++) {
            View view = mHost.getChildAt(i);
            ViewGroup.LayoutParams params = view.getLayoutParams();
            if (Log.isLoggable(TAG, Log.DEBUG)) {
                Log.d(TAG, "should handle measured state too small " + view + " " + params);
            }
            if (params instanceof PercentLayoutParams) {
                PercentLayoutInfo info =
                        ((PercentLayoutParams) params).getPercentLayoutInfo();
                if (info != null) {
                    if (shouldHandleMeasuredWidthTooSmall(view, info)) {
                        needsSecondMeasure = true;
                        params.width = ViewGroup.LayoutParams.WRAP_CONTENT;
                    }
                    if (shouldHandleMeasuredHeightTooSmall(view, info)) {
                        needsSecondMeasure = true;
                        params.height = ViewGroup.LayoutParams.WRAP_CONTENT;
                    }
                }
            }
        }
        if (Log.isLoggable(TAG, Log.DEBUG)) {
            Log.d(TAG, "should trigger second measure pass: " + needsSecondMeasure);
        }
        return needsSecondMeasure;
    }

首先遍历所有的孩子,拿出孩子的layoutparams,如果是PercentLayoutParams实例,则取出info。如果info不为null,调用shouldHandleMeasuredWidthTooSmall判断:

private static boolean shouldHandleMeasuredWidthTooSmall(View view, PercentLayoutInfo info) {
        int state = ViewCompat.getMeasuredWidthAndState(view) & ViewCompat.MEASURED_STATE_MASK;
        return state == ViewCompat.MEASURED_STATE_TOO_SMALL && info.widthPercent >= 0 &&
                info.mPreservedParams.width == ViewGroup.LayoutParams.WRAP_CONTENT;
    }

这里就是判断,如果你设置的measuredWidth或者measureHeight过小的话,并且你在布局文件中layout_w/h 设置的是WRAP_CONTENT的话,将params.width / height= ViewGroup.LayoutParams.WRAP_CONTENT,然后重新测量。

哈,onMeasure终于结束了~~~现在我觉得应该代码结束了吧,尺寸都设置好了,还需要干嘛么,but,你会发现onLayout也重写了,我们又不改变layout规则,在onLayout里面干什么毛线:

@Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        mHelper.restoreOriginalParams();
    }

继续看mHelper.restoreOriginalParams

 /**
     * Iterates over children and restores their original dimensions that were changed for
     * percentage values. Calling this method only makes sense if you previously called
     * {@link PercentLayoutHelper#adjustChildren(int, int)}.
     */
    public void restoreOriginalParams() {
        for (int i = 0, N = mHost.getChildCount(); i < N; i++) {
            View view = mHost.getChildAt(i);
            ViewGroup.LayoutParams params = view.getLayoutParams();
            if (Log.isLoggable(TAG, Log.DEBUG)) {
                Log.d(TAG, "should restore " + view + " " + params);
            }
            if (params instanceof PercentLayoutParams) {
                PercentLayoutInfo info =
                        ((PercentLayoutParams) params).getPercentLayoutInfo();
                if (Log.isLoggable(TAG, Log.DEBUG)) {
                    Log.d(TAG, "using " + info);
                }
                if (info != null) {
                    if (params instanceof ViewGroup.MarginLayoutParams) {
                        info.restoreMarginLayoutParams((ViewGroup.MarginLayoutParams) params);
                    } else {
                        info.restoreLayoutParams(params);
                    }
                }
            }
        }
    }

噗,原来是重新恢复原本的尺寸值,也就是说onMeasure里面的对值进行了改变,测量完成后。在这个地方,将值又恢复成如果布局文件中的值,上面写的都是0。恢复很简单:


public void restoreLayoutParams(ViewGroup.LayoutParams params) {
            params.width = mPreservedParams.width;
            params.height = mPreservedParams.height;
        }

你应该没有忘在哪存的把~忘了的话,麻烦Ctrl+F ‘mPreservedParams.width’ 。

也就是说,你去打印上面写法,布局文件中view的v.getLayoutParams().width,这个值应该是0。

这里感觉略微不爽~这个0没撒用处呀,还不如不重置~~

好了,到此就分析完了,其实主要就几个步骤:

  • LayoutParams中属性的获取
  • onMeasure中,改变params.width为百分比计算结果,测量
  • 如果测量值过小且设置的w/h是wrap_content,重新测量
  • onLayout中,重置params.w/h为布局文件中编写的值

可以看到,有了RelativeLayout、FrameLayout的扩展,竟然没有LinearLayout几个意思。好在,我们的核心代码都由PercentLayoutHelper封装了,自己扩展下LinearLayout也不复杂。


三、实现PercentLinearlayout

可能有人会说,有了weight呀,但是weight能做到宽、高同时百分比赋值嘛?

好了,代码很简单,如下:


(一)PercentLinearLayout

package com.juliengenoud.percentsamples;

import android.content.Context;
import android.content.res.TypedArray;
import android.support.percent.PercentLayoutHelper;
import android.util.AttributeSet;
import android.view.ViewGroup;
import android.widget.LinearLayout;

/**
 * Created by zhy on 15/6/30.
 */
public class PercentLinearLayout extends LinearLayout
{

    private PercentLayoutHelper mPercentLayoutHelper;

    public PercentLinearLayout(Context context, AttributeSet attrs)
    {
        super(context, attrs);

        mPercentLayoutHelper = new PercentLayoutHelper(this);
    }


    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
    {
        mPercentLayoutHelper.adjustChildren(widthMeasureSpec, heightMeasureSpec);
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        if (mPercentLayoutHelper.handleMeasuredStateTooSmall())
        {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        }
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b)
    {
        super.onLayout(changed, l, t, r, b);
        mPercentLayoutHelper.restoreOriginalParams();
    }

    @Override
    public LayoutParams generateLayoutParams(AttributeSet attrs)
    {
        return new LayoutParams(getContext(), attrs);
    }


    public static class LayoutParams extends LinearLayout.LayoutParams
            implements PercentLayoutHelper.PercentLayoutParams
    {
        private PercentLayoutHelper.PercentLayoutInfo mPercentLayoutInfo;

        public LayoutParams(Context c, AttributeSet attrs)
        {
            super(c, attrs);
            mPercentLayoutInfo = PercentLayoutHelper.getPercentLayoutInfo(c, attrs);
        }

        @Override
        public PercentLayoutHelper.PercentLayoutInfo getPercentLayoutInfo()
        {
            return mPercentLayoutInfo;
        }

        @Override
        protected void setBaseAttributes(TypedArray a, int widthAttr, int heightAttr)
        {
            PercentLayoutHelper.fetchWidthAndHeight(this, a, widthAttr, heightAttr);
        }

        public LayoutParams(int width, int height) {
            super(width, height);
        }


        public LayoutParams(ViewGroup.LayoutParams source) {
            super(source);
        }

        public LayoutParams(MarginLayoutParams source) {
            super(source);
        }

    }

}

如果你详细看了上面的源码分析,这个代码是不是没撒解释的了~


(二)测试布局

<?xml version="1.0" encoding="utf-8"?>


<com.juliengenoud.percentsamples.PercentLinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <TextView
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:background="#ff44aacc"
        android:text="width:60%,height:5%"
        android:textColor="#ffffff"
        app:layout_heightPercent="5%"
        app:layout_marginBottomPercent="5%"
        app:layout_widthPercent="60%"/>

    <TextView
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:background="#ff4400cc"
        android:gravity="center"
        android:textColor="#ffffff"
        android:text="width:70%,height:10%"
        app:layout_heightPercent="10%"
        app:layout_marginBottomPercent="5%"
        app:layout_widthPercent="70%"/>
    <TextView
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:background="#ff44aacc"
        android:gravity="center"
        android:text="width:80%,height:15%"
        android:textColor="#ffffff"
        app:layout_heightPercent="15%"
        app:layout_marginBottomPercent="5%"
        app:layout_widthPercent="80%"/>
    <TextView
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:background="#ff4400cc"
        android:gravity="center"
        android:text="width:90%,height:5%"
        android:textColor="#ffffff"
        app:layout_heightPercent="20%"
        app:layout_marginBottomPercent="10%"
        app:layout_widthPercent="90%"/>

    <TextView
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:background="#ff44aacc"
        android:gravity="center"
        android:text="width:100%,height:25%"
        android:textColor="#ffffff"
        app:layout_heightPercent="25%"
        app:layout_marginBottomPercent="5%"
        />


</com.juliengenoud.percentsamples.PercentLinearLayout>

我们纵向排列的几个TextView,分别设置宽/高都为百分比,且之间的间隔为5%p。


(三)效果图

%title插图%num

ok,到此,我们使用、源码分析、扩展PercentLinearLayout就结束了。

Android Studio错误提示

错误异常 (1)Android Studio错误提示:Gradle project sync failed. Basic functionality (eg. editing, debugging) will not work properly

【已解决】

 

【问题】

Android Studio中出现提示:

Gradle project sync failed. Basic functionality (eg. editing, debugging) will not work properly

 

【解决过程】

1.点击了:

Show Log in Explorer

打开找到了log文件:

C:\Users\Administrator\.AndroidStudio\system\log\idea.log

C Users Administrator AndroidStudio system log idea.log file

Log文件内容很长,*后一部分是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
Consult IDE log for more details (Help | Show Log)
2015-04-01 10:37:20,131 [1257636]   INFO - indexing.UnindexedFilesUpdater - Indexable files iterated in 2694 ms
2015-04-01 10:37:20,131 [1257636]   INFO - indexing.UnindexedFilesUpdater - Unindexed files update started: 2500 files to update
2015-04-01 10:37:22,330 [1259835]   INFO - s.impl.stores.FileBasedStorage - Document was not loaded for $PROJECT_CONFIG_DIR$/codeStyleSettings.xml file is null
2015-04-01 10:37:22,333 [1259838]   INFO - s.impl.stores.FileBasedStorage - Document was not loaded for $PROJECT_CONFIG_DIR$/projectCodeStyle.xml file is null
2015-04-01 10:37:22,600 [1260105]   INFO - s.impl.stores.FileBasedStorage - Document was not loaded for $PROJECT_CONFIG_DIR$/fileColors.xml file is null
2015-04-01 10:37:25,627 [1263132]   INFO - s.impl.stores.FileBasedStorage - Document was not loaded for $APP_CONFIG$/web-browsers.xml file is null
2015-04-01 10:37:36,269 [1273774]   INFO - indexing.UnindexedFilesUpdater - Unindexed files update done in 16138 ms
2015-04-01 10:37:36,941 [1274446]   INFO - nject.config.XPathSupportProxy - XPath Support is not available
2015-04-01 10:37:37,119 [1274624]   INFO - s.impl.stores.FileBasedStorage - Document was not loaded for $APP_CONFIG$/IntelliLang.xml file is null
2015-04-01 10:37:37,122 [1274627]   INFO - s.impl.stores.FileBasedStorage - Document was not loaded for $PROJECT_CONFIG_DIR$/IntelliLang.xml file is null
2015-04-01 10:37:37,947 [1275452]   INFO - s.impl.stores.FileBasedStorage - Document was not loaded for $APP_CONFIG$/feature.usage.statistics.xml file is null
2015-04-01 10:37:39,177 [1276682]   INFO - s.impl.stores.FileBasedStorage - Document was not loaded for $PROJECT_CONFIG_DIR$/gant_config.xml file is null
2015-04-01 10:37:40,335 [1277840]   INFO - s.impl.stores.FileBasedStorage - Document was not loaded for $APP_CONFIG$/remote-servers.xml file is null
2015-04-01 10:37:40,518 [1278023]   INFO - tor.impl.FileEditorManagerImpl - Project opening took 58569 ms
2015-04-01 10:37:46,714 [1284219]   INFO - s.impl.stores.FileBasedStorage - Document was not loaded for $APP_CONFIG$/file.template.settings.xml file is null
2015-04-01 10:37:51,540 [1289045]   INFO - s.impl.stores.FileBasedStorage - Document was not loaded for $APP_CONFIG$/cachedDictionary.xml file is null
2015-04-01 10:39:35,962 [1393467]   INFO - s.impl.stores.FileBasedStorage - Document was not loaded for $APP_CONFIG$/intentionSettings.xml file is null
2015-04-01 10:43:56,598 [1654103]   INFO - indexing.UnindexedFilesUpdater - Indexable files iterated in 3182 ms
2015-04-01 10:43:56,598 [1654103]   INFO - indexing.UnindexedFilesUpdater - Unindexed files update started: 22098 files to update
2015-04-01 10:44:23,144 [1680649]   INFO - util.EmbeddedDistributionPaths - Looking for embedded Gradle distribution at 'C:\Program Files\Android\Android Studio\gradle\gradle-2.2.1'
2015-04-01 10:44:23,252 [1680757]   INFO - s.plugins.gradle.GradleManager - Instructing gradle to use java from C:\Program Files\Java\jdk1.7.0_76
2015-04-01 10:44:23,253 [1680758]   INFO - s.plugins.gradle.GradleManager - Instructing gradle to use java from C:\Program Files\Java\jdk1.7.0_76
2015-04-01 10:44:23,254 [1680759]   INFO - util.EmbeddedDistributionPaths - Looking for embedded Maven repo at 'C:\Program Files\Android\Android Studio\gradle\m2repository'
2015-04-01 10:44:23,256 [1680761]   INFO - .project.GradleExecutionHelper - Passing command-line args to Gradle Tooling API: [-Pandroid.injected.build.model.only=true, -Pandroid.injected.build.model.only.advanced=true, -Pandroid.injected.invoked.from.ide=true, --init-script, C:\Users\Administrator\AppData\Local\Temp\asLocalRepo3577806095284007233.gradle, --init-script, C:\Users\Administrator\AppData\Local\Temp\ijinit6508239471401468090.gradle]
2015-04-01 10:44:40,498 [1698003]   INFO - .project.GradleProjectResolver - Gradle project resolve error
org.gradle.tooling.BuildException: Could not run build action using Gradle installation 'C:\Program Files\Android\Android Studio\gradle\gradle-2.2.1'.
 at org.gradle.tooling.internal.consumer.ResultHandlerAdapter.onFailure(ResultHandlerAdapter.java:57)
 at org.gradle.tooling.internal.consumer.async.DefaultAsyncConsumerActionExecutor$1$1.run(DefaultAsyncConsumerActionExecutor.java:57)
 at org.gradle.internal.concurrent.DefaultExecutorFactory$StoppableExecutorImpl$1.run(DefaultExecutorFactory.java:64)
 at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
 at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
 at java.lang.Thread.run(Thread.java:745)
 at org.gradle.tooling.internal.consumer.BlockingResultHandler.getResult(BlockingResultHandler.java:46)
 at org.gradle.tooling.internal.consumer.DefaultBuildActionExecuter.run(DefaultBuildActionExecuter.java:46)
 at org.jetbrains.plugins.gradle.service.project.GradleProjectResolver.doResolveProjectInfo(GradleProjectResolver.java:186)
 at org.jetbrains.plugins.gradle.service.project.GradleProjectResolver.access$300(GradleProjectResolver.java:64)
 at org.jetbrains.plugins.gradle.service.project.GradleProjectResolver$ProjectConnectionDataNodeFunction.fun(GradleProjectResolver.java:361)
 at org.jetbrains.plugins.gradle.service.project.GradleProjectResolver$ProjectConnectionDataNodeFunction.fun(GradleProjectResolver.java:333)
 at org.jetbrains.plugins.gradle.service.project.GradleExecutionHelper.execute(GradleExecutionHelper.java:203)
 at org.jetbrains.plugins.gradle.service.project.GradleProjectResolver.resolveProjectInfo(GradleProjectResolver.java:116)
 at org.jetbrains.plugins.gradle.service.project.GradleProjectResolver.resolveProjectInfo(GradleProjectResolver.java:64)
 at com.intellij.openapi.externalSystem.service.remote.RemoteExternalSystemProjectResolverImpl$1.produce(RemoteExternalSystemProjectResolverImpl.java:41)
 at com.intellij.openapi.externalSystem.service.remote.RemoteExternalSystemProjectResolverImpl$1.produce(RemoteExternalSystemProjectResolverImpl.java:37)
 at com.intellij.openapi.externalSystem.service.remote.AbstractRemoteExternalSystemService.execute(AbstractRemoteExternalSystemService.java:59)
 at com.intellij.openapi.externalSystem.service.remote.RemoteExternalSystemProjectResolverImpl.resolveProjectInfo(RemoteExternalSystemProjectResolverImpl.java:37)
 at com.intellij.openapi.externalSystem.service.remote.wrapper.ExternalSystemProjectResolverWrapper.resolveProjectInfo(ExternalSystemProjectResolverWrapper.java:49)
 at com.intellij.openapi.externalSystem.service.internal.ExternalSystemResolveProjectTask.doExecute(ExternalSystemResolveProjectTask.java:48)
 at com.intellij.openapi.externalSystem.service.internal.AbstractExternalSystemTask.execute(AbstractExternalSystemTask.java:137)
 at com.intellij.openapi.externalSystem.service.internal.AbstractExternalSystemTask.execute(AbstractExternalSystemTask.java:123)
 at com.intellij.openapi.externalSystem.util.ExternalSystemUtil$2.execute(ExternalSystemUtil.java:406)
 at com.intellij.openapi.externalSystem.util.ExternalSystemUtil$3$2.run(ExternalSystemUtil.java:483)
 at com.intellij.openapi.progress.impl.ProgressManagerImpl$TaskRunnable.run(ProgressManagerImpl.java:471)
 at com.intellij.openapi.progress.impl.ProgressManagerImpl$2.run(ProgressManagerImpl.java:178)
 at com.intellij.openapi.progress.ProgressManager.executeProcessUnderProgress(ProgressManager.java:209)
 at com.intellij.openapi.progress.impl.ProgressManagerImpl.executeProcessUnderProgress(ProgressManagerImpl.java:212)
 at com.intellij.openapi.progress.impl.ProgressManagerImpl.runProcess(ProgressManagerImpl.java:171)
 at com.intellij.openapi.progress.impl.ProgressManagerImpl$8.run(ProgressManagerImpl.java:380)
 at com.intellij.openapi.application.impl.ApplicationImpl$8.run(ApplicationImpl.java:419)
 at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:471)
 at java.util.concurrent.FutureTask.run(FutureTask.java:262)
 at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
 at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
 at java.lang.Thread.run(Thread.java:745)
 at com.intellij.openapi.application.impl.ApplicationImpl$1$1.run(ApplicationImpl.java:149)
Caused by: org.gradle.internal.exceptions.LocationAwareException: A problem occurred configuring project ':cSipSimplemaster'.
 at org.gradle.initialization.DefaultExceptionAnalyser.transform(DefaultExceptionAnalyser.java:77)
 at org.gradle.initialization.MultipleBuildFailuresExceptionAnalyser.transform(MultipleBuildFailuresExceptionAnalyser.java:47)
 at org.gradle.initialization.StackTraceSanitizingExceptionAnalyser.transform(StackTraceSanitizingExceptionAnalyser.java:30)
 at org.gradle.initialization.DefaultGradleLauncher.doBuild(DefaultGradleLauncher.java:108)
 at org.gradle.initialization.DefaultGradleLauncher.getBuildAnalysis(DefaultGradleLauncher.java:97)
 at org.gradle.launcher.exec.InProcessBuildActionExecuter$DefaultBuildController.configure(InProcessBuildActionExecuter.java:84)
 at org.gradle.tooling.internal.provider.ClientProvidedBuildAction.run(ClientProvidedBuildAction.java:43)
 at org.gradle.tooling.internal.provider.ClientProvidedBuildAction.run(ClientProvidedBuildAction.java:31)
 at org.gradle.tooling.internal.provider.ConfiguringBuildAction.run(ConfiguringBuildAction.java:119)
 at org.gradle.launcher.exec.InProcessBuildActionExecuter.execute(InProcessBuildActionExecuter.java:36)
 at org.gradle.launcher.exec.InProcessBuildActionExecuter.execute(InProcessBuildActionExecuter.java:26)
 at org.gradle.launcher.daemon.server.exec.ExecuteBuild.doBuild(ExecuteBuild.java:47)
 at org.gradle.launcher.daemon.server.exec.BuildCommandOnly.execute(BuildCommandOnly.java:34)
 at org.gradle.launcher.daemon.server.exec.DaemonCommandExecution.proceed(DaemonCommandExecution.java:119)
 at org.gradle.launcher.daemon.server.exec.WatchForDisconnection.execute(WatchForDisconnection.java:35)
 at org.gradle.launcher.daemon.server.exec.DaemonCommandExecution.proceed(DaemonCommandExecution.java:119)
 at org.gradle.launcher.daemon.server.exec.ResetDeprecationLogger.execute(ResetDeprecationLogger.java:24)
 at org.gradle.launcher.daemon.server.exec.DaemonCommandExecution.proceed(DaemonCommandExecution.java:119)
 at org.gradle.launcher.daemon.server.exec.StartStopIfBuildAndStop.execute(StartStopIfBuildAndStop.java:33)
 at org.gradle.launcher.daemon.server.exec.DaemonCommandExecution.proceed(DaemonCommandExecution.java:119)
 at org.gradle.launcher.daemon.server.exec.ForwardClientInput$2.call(ForwardClientInput.java:71)
 at org.gradle.launcher.daemon.server.exec.ForwardClientInput$2.call(ForwardClientInput.java:69)
 at org.gradle.util.Swapper.swap(Swapper.java:38)
 at org.gradle.launcher.daemon.server.exec.ForwardClientInput.execute(ForwardClientInput.java:69)
 at org.gradle.launcher.daemon.server.exec.DaemonCommandExecution.proceed(DaemonCommandExecution.java:119)
 at org.gradle.launcher.daemon.server.exec.LogToClient.doBuild(LogToClient.java:60)
 at org.gradle.launcher.daemon.server.exec.BuildCommandOnly.execute(BuildCommandOnly.java:34)
 at org.gradle.launcher.daemon.server.exec.DaemonCommandExecution.proceed(DaemonCommandExecution.java:119)
 at org.gradle.launcher.daemon.server.exec.EstablishBuildEnvironment.doBuild(EstablishBuildEnvironment.java:70)
 at org.gradle.launcher.daemon.server.exec.BuildCommandOnly.execute(BuildCommandOnly.java:34)
 at org.gradle.launcher.daemon.server.exec.DaemonCommandExecution.proceed(DaemonCommandExecution.java:119)
 at org.gradle.launcher.daemon.server.exec.DaemonHygieneAction.execute(DaemonHygieneAction.java:39)
 at org.gradle.launcher.daemon.server.exec.DaemonCommandExecution.proceed(DaemonCommandExecution.java:119)
 at org.gradle.launcher.daemon.server.exec.StartBuildOrRespondWithBusy$1.run(StartBuildOrRespondWithBusy.java:46)
 at org.gradle.launcher.daemon.server.DaemonStateCoordinator$1.run(DaemonStateCoordinator.java:246)
 at org.gradle.internal.concurrent.DefaultExecutorFactory$StoppableExecutorImpl$1.run(DefaultExecutorFactory.java:64)
Caused by: org.gradle.api.ProjectConfigurationException: A problem occurred configuring project ':cSipSimplemaster'.
 at org.gradle.configuration.project.LifecycleProjectEvaluator.addConfigurationFailure(LifecycleProjectEvaluator.java:91)
 at org.gradle.configuration.project.LifecycleProjectEvaluator.notifyAfterEvaluate(LifecycleProjectEvaluator.java:86)
 at org.gradle.configuration.project.LifecycleProjectEvaluator.evaluate(LifecycleProjectEvaluator.java:65)
 at org.gradle.api.internal.project.AbstractProject.evaluate(AbstractProject.java:504)
 at org.gradle.api.internal.project.AbstractProject.evaluate(AbstractProject.java:83)
 at org.gradle.execution.TaskPathProjectEvaluator.configureHierarchy(TaskPathProjectEvaluator.java:47)
 at org.gradle.configuration.DefaultBuildConfigurer.configure(DefaultBuildConfigurer.java:35)
 at org.gradle.initialization.DefaultGradleLauncher.doBuildStages(DefaultGradleLauncher.java:129)
 at org.gradle.initialization.DefaultGradleLauncher.doBuild(DefaultGradleLauncher.java:106)
 ... 32 more
Caused by: java.lang.IllegalStateException: failed to find target android-19 : C:\Users\Administrator\AppData\Local\Android\sdk
 at com.android.builder.sdk.DefaultSdkLoader.getTargetInfo(DefaultSdkLoader.java:88)
 at com.android.build.gradle.internal.SdkHandler.initTarget(SdkHandler.java:90)
 at com.android.build.gradle.BasePlugin.ensureTargetSetup(BasePlugin.groovy:467)
 at com.android.build.gradle.BasePlugin.access$0(BasePlugin.groovy)
 at com.android.build.gradle.BasePlugin$_createTasks_closure9.doCall(BasePlugin.groovy:372)
 at org.gradle.listener.ClosureBackedMethodInvocationDispatch.dispatch(ClosureBackedMethodInvocationDispatch.java:40)
 at org.gradle.listener.ClosureBackedMethodInvocationDispatch.dispatch(ClosureBackedMethodInvocationDispatch.java:25)
 at org.gradle.listener.BroadcastDispatch.dispatch(BroadcastDispatch.java:83)
 at org.gradle.listener.BroadcastDispatch.dispatch(BroadcastDispatch.java:31)
 at org.gradle.messaging.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:93)
 at com.sun.proxy.$Proxy12.afterEvaluate(Unknown Source)
 at org.gradle.configuration.project.LifecycleProjectEvaluator.notifyAfterEvaluate(LifecycleProjectEvaluator.java:79)
 ... 39 more
2015-04-01 10:44:40,500 [1698005]   WARN - nal.AbstractExternalSystemTask - failed to find target android-19 : C:\Users\Administrator\AppData\Local\Android\sdk
com.intellij.openapi.externalSystem.model.ExternalSystemException: failed to find target android-19 : C:\Users\Administrator\AppData\Local\Android\sdk
 at com.android.tools.idea.gradle.project.ProjectImportErrorHandler.createUserFriendlyError(ProjectImportErrorHandler.java:225)
 at com.android.tools.idea.gradle.project.ProjectImportErrorHandler.getUserFriendlyError(ProjectImportErrorHandler.java:103)
 at com.android.tools.idea.gradle.project.AndroidGradleProjectResolver.getUserFriendlyError(AndroidGradleProjectResolver.java:321)
 at org.jetbrains.plugins.gradle.service.project.GradleProjectResolver$ProjectConnectionDataNodeFunction.fun(GradleProjectResolver.java:367)
 at org.jetbrains.plugins.gradle.service.project.GradleProjectResolver$ProjectConnectionDataNodeFunction.fun(GradleProjectResolver.java:333)
 at org.jetbrains.plugins.gradle.service.project.GradleExecutionHelper.execute(GradleExecutionHelper.java:203)
 at org.jetbrains.plugins.gradle.service.project.GradleProjectResolver.resolveProjectInfo(GradleProjectResolver.java:116)
 at org.jetbrains.plugins.gradle.service.project.GradleProjectResolver.resolveProjectInfo(GradleProjectResolver.java:64)
 at com.intellij.openapi.externalSystem.service.remote.RemoteExternalSystemProjectResolverImpl$1.produce(RemoteExternalSystemProjectResolverImpl.java:41)
 at com.intellij.openapi.externalSystem.service.remote.RemoteExternalSystemProjectResolverImpl$1.produce(RemoteExternalSystemProjectResolverImpl.java:37)
 at com.intellij.openapi.externalSystem.service.remote.AbstractRemoteExternalSystemService.execute(AbstractRemoteExternalSystemService.java:59)
 at com.intellij.openapi.externalSystem.service.remote.RemoteExternalSystemProjectResolverImpl.resolveProjectInfo(RemoteExternalSystemProjectResolverImpl.java:37)
 at com.intellij.openapi.externalSystem.service.remote.wrapper.ExternalSystemProjectResolverWrapper.resolveProjectInfo(ExternalSystemProjectResolverWrapper.java:49)
 at com.intellij.openapi.externalSystem.service.internal.ExternalSystemResolveProjectTask.doExecute(ExternalSystemResolveProjectTask.java:48)
 at com.intellij.openapi.externalSystem.service.internal.AbstractExternalSystemTask.execute(AbstractExternalSystemTask.java:137)
 at com.intellij.openapi.externalSystem.service.internal.AbstractExternalSystemTask.execute(AbstractExternalSystemTask.java:123)
 at com.intellij.openapi.externalSystem.util.ExternalSystemUtil$2.execute(ExternalSystemUtil.java:406)
 at com.intellij.openapi.externalSystem.util.ExternalSystemUtil$3$2.run(ExternalSystemUtil.java:483)
 at com.intellij.openapi.progress.impl.ProgressManagerImpl$TaskRunnable.run(ProgressManagerImpl.java:471)
 at com.intellij.openapi.progress.impl.ProgressManagerImpl$2.run(ProgressManagerImpl.java:178)
 at com.intellij.openapi.progress.ProgressManager.executeProcessUnderProgress(ProgressManager.java:209)
 at com.intellij.openapi.progress.impl.ProgressManagerImpl.executeProcessUnderProgress(ProgressManagerImpl.java:212)
 at com.intellij.openapi.progress.impl.ProgressManagerImpl.runProcess(ProgressManagerImpl.java:171)
 at com.intellij.openapi.progress.impl.ProgressManagerImpl$8.run(ProgressManagerImpl.java:380)
 at com.intellij.openapi.application.impl.ApplicationImpl$8.run(ApplicationImpl.java:419)
 at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:471)
 at java.util.concurrent.FutureTask.run(FutureTask.java:262)
 at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
 at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
 at java.lang.Thread.run(Thread.java:745)
 at com.intellij.openapi.application.impl.ApplicationImpl$1$1.run(ApplicationImpl.java:149)
2015-04-01 10:44:40,500 [1698005]   WARN - radle.project.ProjectSetUpTask - 
2015-04-01 10:44:40,501 [1698006]   INFO - radle.project.ProjectSetUpTask - failed to find target android-19 : C:\Users\Administrator\AppData\Local\Android\sdk
Consult IDE log for more details (Help | Show Log)
2015-04-01 10:44:59,763 [1717268]   INFO - ellij.vfs.persistent.FSRecords - Contents:16384 of 144781555, reuses:82 of 73161 for 1173
2015-04-01 10:45:13,819 [1731324]   INFO - indexing.UnindexedFilesUpdater - Unindexed files update done in 77221 ms
2015-04-01 11:05:26,225 [2943730]   INFO - s.impl.stores.FileBasedStorage - Document was not loaded for $APP_CONFIG$/emmet.xml file is null
2015-04-01 11:05:26,335 [2943840]   INFO - s.impl.stores.FileBasedStorage - Document was not loaded for $APP_CONFIG$/postfixTemplates.xml file is null

2.对应着,Android Studio中也有错误提示:

1
2
Error:failed to find target android-19 : C:\Users\Administrator\AppData\Local\Android\sdk
<a href="install.android.platform">Install missing platform(s) and sync project</a>

failed to sync gradle project CSipSimple-master

很明显:

好像是由于:

没有安装android的image,所以找不到android-19.

抽空再去安装android的image

3.然后点击了:

Install missing platform(s) and sync project

后,去安装:

SDK quick installation android sdk platform 19

installing requested componets for android sdk

对应log:

1
2
3
4
5
6
7
8
9
10
11
12
13
Loading SDK information...
Refresh Sources:
  Fetched Add-ons List successfully
  Refresh Sources
  Failed to fetch URL https://dl-ssl.google.com/glass/gdk/addon.xml, reason: Connect Connection timed out: connect
Refresh Sources:
  Failed to fetch URL https://dl-ssl.google.com/glass/gdk/addon.xml, reason: Connect Connection timed out: connect
Installing Archives:
  Preparing to install archives
  Installing SDK Platform Android 4.4.2, API 19, revision 4
    Installed SDK Platform Android 4.4.2, API 19, revision 4
  Done. 1 package installed.

 

【总结】

此处Android Studio中出现

Error:failed to find target android-19

是因为没有安装Android SDK Platform 19(Android 4.4.2 API 19),去按照提示安装好即可消除此错误。

Android Studio中提示:Project SDK is not defined

Android Studio中提示:Project SDK is not defined
 

【背景】

之前用Android Studio去打开一个项目后,结果遇到提示:

Project SDK is not defined

如图:

android studio Project SDK is not defined

【解决过程】

1.试着去点击Setup SDK,出现设置对话框:

popup select project sdk choose android api 21 platform

然后就卡死了好一会。

然后就好了,就没了这个提示了。

 

【总结】

好像是Android Studio对于刚使用的时候,没有确定Android的SDK,所以需要去设置一下对应的所用的版本。

此处选择默认的Android API 21 Platform(java version 1.7.0_76)

即可。

其中java version 1.7.0_76是此处本地所安装的java(jdk)的版本。

Java中正则Matcher类的matches()、lookAt()和find()的区别

Java中正则Matcher类的matches()、lookAt()和find()的区别

在Matcher类中有matches、lookingAt和find都是匹配目标的方法,但容易混淆,整理它们的区别如下:

  • matches:整个匹配,只有整个字符序列完全匹配成功,才返回True,否则返回False。但如果前部分匹配成功,将移动下次匹配的位置。
  • lookingAt:部分匹配,总是从*个字符进行匹配,匹配成功了不再继续匹配,匹配失败了,也不继续匹配。
  • find:部分匹配,从当前位置开始匹配,找到一个匹配的子串,将移动下次匹配的位置。
  • reset:给当前的Matcher对象配上个新的目标,目标是就该方法的参数;如果不给参数,reset会把Matcher设到当前字符串的开始处。

使用示例代码来展示他们的区别更清晰明了:

  1. package net.oseye;
  2.  
  3. import java.util.regex.Matcher;
  4. import java.util.regex.Pattern;
  5.  
  6. public class IOTest {
  7. public static void main(String[] args){
  8. Pattern pattern = Pattern.compile(“\\d{3,5}”);
  9. String charSequence “123-34345-234-00”;
  10. Matcher matcher = pattern.matcher(charSequence);
  11.  
  12. //虽然匹配失败,但由于charSequence里面的”123″和pattern是匹配的,所以下次的匹配从位置4开始
  13. print(matcher.matches());
  14. //测试匹配位置
  15. matcher.find();
  16. print(matcher.start());
  17.  
  18. //使用reset方法重置匹配位置
  19. matcher.reset();
  20.  
  21. //*次find匹配以及匹配的目标和匹配的起始位置
  22. print(matcher.find());
  23. print(matcher.group()+” – “+matcher.start());
  24. //第二次find匹配以及匹配的目标和匹配的起始位置
  25. print(matcher.find());
  26. print(matcher.group()+” – “+matcher.start());
  27.  
  28. //*次lookingAt匹配以及匹配的目标和匹配的起始位置
  29. print(matcher.lookingAt());
  30. print(matcher.group()+” – “+matcher.start());
  31.  
  32. //第二次lookingAt匹配以及匹配的目标和匹配的起始位置
  33. print(matcher.lookingAt());
  34. print(matcher.group()+” – “+matcher.start());
  35. }
  36. public static void print(Object o){
  37. System.out.println(o);
  38. }
  39. }

输出结果:

    1. false
    2. 4
    3. true
    4. 123 – 0
    5. true
    6. 34345 – 4
    7. true
    8. 123 – 0
    9. true
    10. 123 – 0

正则表达式的语法规则

正则表达式的语法规则

一、行定位符(^和$)

行定位符就是用来描述字串的边界。“^”表示行的开始;“$”表示行的结尾。如:

^tm : 该表达式表示要匹配字串tm的开始位置是行头,如tm equal Tomorrow Moon就可以匹配

tm$ : 该表达式表示要匹配字串tm的位置是行尾,Tomorrow Moon equal tm匹配。

如果要匹配的字串可以出现在字符串的任意部分,那么可以直接   写成 :tm

二、单词定界符(\b、\B)

单词分界符\b,表示要查找的字串为一个完整的单词。如:\btm\b

还有一个大写的\B,意思和\b相反。它匹配的字串不能是一个完整的单词,而是其他单词或字串的一部分。如:\Btm\B

三、字符类([ ])

正则表达式是区分大小写的,如果要忽略大小写可使用方括号表达式“[]”。只要匹配的字符出现在方括号内,即可表示匹配成功。但要注意:一个方括号只能匹配一个字符。例如,要匹配的字串tm不区分大小写,那么该表达式应该写作如下格式:[Tt][Mm]

POSIX风格的预定义字符类如表所示:

%title插图%num

四、选择字符(|)

还有一种方法可以实现上面的匹配模式,就是使用选择字符(|)。该字符可以理解为“或”,如上例也可以写成 (T|t)(M|m),该表达式的意思是以字母T或t开头,后面接一个字母M或m。

使用“[]”和使用“|”的区别在于“[]”只能匹配单个字符,而“|”可以匹配任意长度的字串。如果不怕麻烦,上例还可以写为 :TM|tm|Tm|tM

 

五、连字符(-)

变量的命名规则是只能以字母和下划线开头。但这样一来,如果要使用正则表达式来匹配变量名的*个字母,要写为 :[a,b,c,d…A,B,C,D…]

这无疑是非常麻烦的,正则表达式提供了连字符“-”来解决这个问题。连字符可以表示字符的范围。如上例可以写成 :[a-zA-Z]

六、排除字符([^])

上面的例子是匹配符合命名规则的变量。现在反过来,匹配不符合命名规则的变量,正则表达式提供了“^”字符。这个元字符在前面出现过,表示行的开始。而这里将会放到方括号中,表示排除的意思。

例如:[^a-zA-Z],该表达式匹配的就是不以字母和下划线开头的变量名。

七、限定符(? * + {n,m})

对于重复出现字母或字串,可以使用限定符来实现匹配。限定符主要有6种,如表所示:

%title插图%num

八、点号字符(.)

点字符(.)可以匹配出换行符外的任意一个字符

注意:是除了换行符外的、任意的一个字符。如匹配以s开头、t结尾、中间包含一个字母的单词。

格式如下: ^s.t$,匹配的单词包括:sat、set、sit等。

再举一个实例,匹配一个单词,它的*个字母为r,第3个字母为s,*后一个字母为t。能匹配该单词的正则表达式为:^r.s.*t$

九、转义字符(\)

正则表达式中的转移字符(\)和PHP中的大同小异,都是将特殊字符(如“.”、“?”、“\”等)变为普通的字符。举一个IP地址的实例,用正则表达式匹配诸如127.0.0.1这样格式的IP地址。如果直接使用点字符,格式为:[0-9]{1,3}(.[0-9]{1,3}){3}

这显然不对,因为“.”可以匹配一个任意字符。这时,不仅是127.0.0.1这样的IP,连127101011这样的字串也会被匹配出来。所以在使用“.”时,需要使用转义字符(\)。修改后上面的正则表达式格式为: [0-9]{1,3}(\.[0-9]{1,3}){3}

十、反斜线(\)

除了可以做转义字符外,反斜线还有其他一些功能。反斜线可以将一些不可打印的字符显示出来,如表所示:

%title插图%num

还可以指定预定义字符集,如表所示:

%title插图%num

反斜线还有一种功能,就是定义断言,其中已经了解过了\b、\B,其他如表所示:

%title插图%num

十一、括号字符(())

小括号字符的*个作用就是可以改变限定符的作用范围,如“|”、“*”、“^”等。来看下面的一个表达式。

(thir|four)th,这个表达式的意思是匹配单词thirth或fourth,如果不使用小括号,那么就变成了匹配单词thir和fourth了。

小括号的第二个作用是分组,也就是子表达式。如(\.[0-9]{1,3}){3},就是对分组(\.[0-9]{1,3})进行重复操作。后面要学到的反向引用和分组有着直接的关系。

十二、反向引用

十三、模式修饰符

模式修饰符的作用是设定模式。也就是规定正则表达式应该如何解释和应用。

不同的语言都有自己的模式设置,PHP中的主要模式如表所示:

%title插图%num

View (二) 自定义属性 自定义属性的格式详解

View (二) 自定义属性 自定义属性的格式详解

自定义属性格式一共有十种;

1. reference:参考某一资源ID。

2. color:颜色值。

3. boolean:布尔值。

4. dimension:尺寸值。

5. float:浮点值。

6. integer:整型值。

7. string:字符串。

8. fraction:百分数。

9. enum:枚举值。

10. flag:位或运算。

 

1. reference:参考某一资源ID。

 

(1)属性定义:

 

            <declare-styleable name = "名称">

                   <attr name = "background" format = "reference" />

            </declare-styleable>

 

(2)属性使用:

 

复制代码
        <ImageView

                     android:layout_width = "42dip"
                     android:layout_height = "42dip"
                     android:background = "@drawable/图片ID"

                     />
复制代码

 

2. color:颜色值。

 

(1)属性定义:

 
            <declare-styleable name = "名称">

                   <attr name = "textColor" format = "color" />

            </declare-styleable>

 

(2)属性使用:

 <TextView
 android:layout_width = "42dip"
 android:layout_height = "42dip"
 android:textColor = "#00FF00"/>

3. boolean:布尔值。

 

(1)属性定义:

            <declare-styleable name = "名称">

             <attr name = "focusable" format = "boolean" />

            </declare-styleable>

(2)属性使用:

 

 

复制代码
 <Button

                    android:layout_width = "42dip"
                    android:layout_height = "42dip"

                    android:focusable = "true"

                    />
复制代码

 

 

4. dimension:尺寸值。

 

(1)属性定义:

 

 

    <declare-styleable name = "名称">

                   <attr name = "layout_width" format = "dimension" />

            </declare-styleable>

 

 

(2)属性使用:

 <Button
 android:layout_width = "42dip"                    
  android:layout_height = "42dip"
  />

5. float:浮点值。

 

(1)属性定义:

 

 

 <declare-styleable name = "AlphaAnimation">
 <attr name = "fromAlpha" format = "float" />                   
<attr name = "toAlpha" format = "float" />         
 </declare-styleable>

 

 

(2)属性使用:

 

 

   <alpha
     android:fromAlpha = "1.0"
     android:toAlpha = "0.7"                  
 />

 

 

6. integer:整型值。

 

(1)属性定义:

 

 

复制代码
<declare-styleable name = "AnimatedRotateDrawable">
 <attr name = "visible" />
 <attr name = "frameDuration" format="integer" />                   
<attr name = "framesCount" format="integer" />                   
<attr name = "pivotX" />                   
<attr name = "pivotY" />                   
<attr name = "drawable" />           
</declare-styleable>
复制代码

 

 

(2)属性使用:

 

复制代码
            <animated-rotate
                   xmlns:android = "http://schemas.android.com/apk/res/android" 
                   android:drawable = "@drawable/图片ID" 
                   android:pivotX = "50%" 
                   android:pivotY = "50%" 
                   android:framesCount = "12"                     
                   android:frameDuration = "100"
                   />
复制代码

 

 

7. string:字符串。

 

(1)属性定义:

 

 

<declare-styleable name = "MapView">
<attr name = "apiKey" format = "string" />            
</declare-styleable>

 

 

 

(2)属性使用:

 

 

<com.google.android.maps.MapView
  android:layout_width = "fill_parent"
  android:layout_height = "fill_parent"
   android:apiKey = "0jOkQ80oD1JL9C6HAja99uGXCRiS2CGjKO_bc_g"/>

 

 

8. fraction:百分数。

 

(1)属性定义:

 

 

复制代码
<declare-styleable name="RotateDrawable">
 <attr name = "visible" />
 <attr name = "fromDegrees" format = "float" />
<attr name = "toDegrees" format = "float" />
<attr name = "pivotX" format = "fraction" />                   
<attr name = "pivotY" format = "fraction" />                   
<attr name = "drawable" />            
</declare-styleable>
复制代码

 

 

 

(2)属性使用:

 

 

复制代码
<rotate
xmlns:android = "http://schemas.android.com/apk/res/android"
android:interpolator = "@anim/动画ID"
android:fromDegrees = "0"
android:toDegrees = "360"
android:pivotX = "200%"
android:pivotY = "300%"                
android:duration = "5000"
android:repeatMode = "restart"
android:repeatCount = "infinite"
/>
复制代码

 

 

9. enum:枚举值。

 

(1)属性定义:

 

 

 <declare-styleable name="名称">
<attr name="orientation">                          
<enum name="horizontal" value="0" />                          
<enum name="vertical" value="1" />                  
 </attr>            
</declare-styleable>

 

 

(2)属性使用:

 

 

<LinearLayout
xmlns:android = "http://schemas.android.com/apk/res/android"
android:orientation = "vertical"                    
android:layout_width = "fill_parent"                   
android:layout_height = "fill_parent"  >            
</LinearLayout>

 

 

10. flag:位或运算。

 

(1)属性定义:

 

 

复制代码
 <declare-styleable name="名称">
 <attr name="windowSoftInputMode">                            
<flag name = "stateUnspecified" value = "0" />                            
<flag name = "stateUnchanged" value = "1" />                            
<flag name = "stateHidden" value = "2" />                            
<flag name = "stateAlwaysHidden" value = "3" />                            
<flag name = "stateVisible" value = "4" />                            
<flag name = "stateAlwaysVisible" value = "5" />                            
<flag name = "adjustUnspecified" value = "0x00" />                            
<flag name = "adjustResize" value = "0x10" />                            
<flag name = "adjustPan" value = "0x20" />                            
<flag name = "adjustNothing" value = "0x30" />                     
</attr>         
</declare-styleable>
复制代码

 

 

(2)属性使用:

 

 

复制代码
 <activity
android:name = ".StyleAndThemeActivity"                   
android:label = "@string/app_name"
android:windowSoftInputMode = "stateUnspecified | stateUnchanged | stateHidden">                   
<intent-filter>                          
<action android:name = "android.intent.action.MAIN" />                          
<category android:name = "android.intent.category.LAUNCHER" />                   
</intent-filter>             
</activity>
复制代码

 

 

注意:

 

属性定义时可以指定多种类型值。

 

(1)属性定义:

 

 

<declare-styleable name = "名称">
<attr name = "background" format = "reference|color" />
</declare-styleable>

 

 

(2)属性使用:

 

 

 <ImageView
android:layout_width = "42dip"
android:layout_height = "42dip"
android:background = "@drawable/图片ID|#00FF00"
/>

View (二) 自定义属性

View (二) 自定义属性

主要有三种方法可以实现自定义属性。
方法一:不使用命名空间,不使用attrs.xml文件。通过attrs.getAttributeResourceValue方法拿到属性值
方法二: 使用命名空间, 不使用attrs.xml文件。通过attrs.getAttributeResourceValue方法拿到属性值
方法三: 使用命名空间,   使用attrs.xml文件。通过context.obtainStyledAttributes(attrs,R.styleable.ImageTextView).getString()方法拿到属性值
*种方法使用*简单,但获取的属性值多为字符串不能获取各种类型的值,第三种方法是用步骤多些,但可以获取各种类型的属性值,并且可以提供代码检错功能

*种方法,直接设置属性值,通过attrs.getAttributeResourceValue拿到这个属性值。

(1)在xml文件中设置属性值

复制代码
<LinearLayout 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:orientation="vertical"
    android:gravity="center"
    tools:context=".MainActivity">
    <com.example.lenovo.custom_textview.ImageTextView1
        android:id="@+id/itv"
        iamgeBoolean="true"
        iamgeColor="#00ff00"
        iamgeDimension="100dp"
        iamgeEnum1="enum2"
        iamgeFlag="flag3"
        iamgeFloat="0.8"
        iamgeFraction="200%p"
        iamgeInteger="100"
        iamgeString="自定义属性"
        imageReference="@drawable/trash"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
</LinearLayout>
复制代码

 

(2)在构造函数中拿到这个值

 

复制代码
package com.example.lenovo.custom_textview;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.widget.TextView;

/**
 * Created by lenovo on 2016/2/18.
 */
public class ImageTextView1 extends TextView {
    public ImageTextView1(Context context) {
        super(context);
    }
    public ImageTextView1(Context context, AttributeSet attrs) {
        super(context, attrs);
        //可以获取所有属性值的字符串表示,int,flat,boolea,reference,string 类型能获取准确值
        String iamgeDimension = attrs.getAttributeValue(null, "iamgeDimension");
        int imageReference = attrs.getAttributeResourceValue(null, "imageReference", 0);
        if (imageReference > 0) {
            bitmap = BitmapFactory.decodeResource(getResources(), imageReference);
        }
        String iamgeColor = attrs.getAttributeValue(null, "iamgeColor");
        String iamgeString = attrs.getAttributeValue(null, "iamgeString");
        int iamgeInteger = attrs.getAttributeIntValue(null, "iamgeInteger", 0);
        float iamgeFloat = attrs.getAttributeFloatValue(null, "iamgeFloat", 0);
        boolean iamgeBoolean = attrs.getAttributeBooleanValue(null, "iamgeBoolean", false);
        String iamgeFraction = attrs.getAttributeValue(null, "iamgeFraction");
        String iamgeEnum1 = attrs.getAttributeValue(null, "iamgeEnum1");
        String iamgeFlag = attrs.getAttributeValue(null, "iamgeFlag");

        StringBuffer str = new StringBuffer();
        str.append("iamgeDimension=  " + iamgeDimension + "\n");
        str.append("imageReference=  " + imageReference + "\n");
        str.append("iamgeColor=  " + iamgeColor + "\n");
        str.append("iamgeBoolean=  " + iamgeBoolean + "\n");
        str.append("iamgeString=  " + iamgeString + "\n");
        str.append("iamgeInteger=  " + iamgeInteger + "\n");
        str.append("iamgeFloat=  " + iamgeFloat + "\n");
        str.append("iamgeFraction=  " + iamgeFraction + "\n");
        str.append("iamgeEnum1=  " + iamgeEnum1 + "\n");
        str.append("iamgeFlag=  " + iamgeFlag + "\n");
        setText(str.toString());

    }

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

    private Bitmap bitmap;

    @Override
    public void onDraw(Canvas canvas) {
        if (bitmap != null) {
            Rect src = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight());

            Rect target = new Rect();
            int textHeight = (int) getTextSize();
            target.left = 0;
            target.top = (int) (getMeasuredHeight() - getTextSize()) / 2 + 1;
            target.bottom = target.top + textHeight;
            target.right = (int) (textHeight * (bitmap.getWidth() / (float) bitmap.getHeight()));
            canvas.drawBitmap(bitmap, src, target, getPaint());
            canvas.translate(target.right + 2, 0);
        }

        super.onDraw(canvas);
    }


}
复制代码

 

结果:

%title插图%num

第二种方法,使用自己的命名空间,通过attrs.getAttributeResourceValue拿到这个属性值。

(1)注意在xml文件中,需要声明一个命名空间,形式一般为为http:// + 这个view的包名(其实这个名字可以随便取名只要是个名字就行,只是一般遵循这个格式)如果为http://com.example.activity,注意的是xml 中的命名空间名字要和获取属性attrs.getAttributeValue(“http://com.example.activity”, “iamgeDimension”)中的命名空间名字一样

 

复制代码
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:mobile="http://com.example.activity"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    android:orientation="vertical"
    tools:context=".MainActivity">
    <com.example.lenovo.custom_textview.ImageTextView2
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        mobile:iamgeBoolean="true"
        mobile:iamgeColor="@color/material_blue_grey_800"
        mobile:iamgeDimension="100dp"
        mobile:iamgeEnum1="enum2"
        mobile:iamgeFlag="flag3"
        mobile:iamgeFloat="0.8"
        mobile:iamgeFraction="200%p"
        mobile:iamgeInteger="100"
        mobile:iamgeString="自定义属性"
        mobile:imageReference="@drawable/trash" />
</LinearLayout>
复制代码

 

(2)通过attrs.getAttributeResourceValue,其中*个参数为命名空间。

复制代码
package com.example.lenovo.custom_textview;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.widget.TextView;

/**
 * Created by lenovo on 2016/2/18.
 */
public class ImageTextView2 extends TextView {
    public ImageTextView2(Context context) {
        super(context);
    }
    //命名空间
    private final String namespace = "http://com.example.activity";
    String tag = "ldq";
    public ImageTextView2(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    public ImageTextView2(Context context, AttributeSet attrs) {
        super(context, attrs);

        //可以获取所有属性值的字符串表示,int,flat,boolea,reference,string 类型能获取准确值
        String iamgeDimension = attrs.getAttributeValue(namespace, "iamgeDimension");
        int imageReference = attrs.getAttributeResourceValue(namespace, "imageReference", 0);
        if (imageReference > 0) {
            bitmap = BitmapFactory.decodeResource(getResources(), imageReference);
        }
        String iamgeColor = attrs.getAttributeValue(namespace, "iamgeColor");
        String iamgeString = attrs.getAttributeValue(namespace, "iamgeString");
        int iamgeInteger = attrs.getAttributeIntValue(namespace, "iamgeInteger", 0);
        float iamgeFloat = attrs.getAttributeFloatValue(namespace, "iamgeFloat", 0);
        boolean iamgeBoolean = attrs.getAttributeBooleanValue(namespace, "iamgeBoolean", false);
        String iamgeFraction = attrs.getAttributeValue(namespace, "iamgeFraction");
        String iamgeEnum1 = attrs.getAttributeValue(namespace, "iamgeEnum1");
        String iamgeFlag = attrs.getAttributeValue(namespace, "iamgeFlag");

        StringBuffer str = new StringBuffer();
        str.append("iamgeDimension=  " + iamgeDimension + "\n");
        str.append("imageReference=  " + imageReference + "\n");
        str.append("iamgeColor=  " + iamgeColor + "\n");
        str.append("iamgeBoolean=  " + iamgeBoolean + "\n");
        str.append("iamgeString=  " + iamgeString + "\n");
        str.append("iamgeInteger=  " + iamgeInteger + "\n");
        str.append("iamgeFloat=  " + iamgeFloat + "\n");
        str.append("iamgeFraction=  " + iamgeFraction + "\n");
        str.append("iamgeEnum1=  " + iamgeEnum1 + "\n");
        str.append("iamgeFlag=  " + iamgeFlag + "\n");
       setText(str.toString());
    }


    private Bitmap bitmap;

    @Override
    public void onDraw(Canvas canvas) {
        if (bitmap != null) {
            Rect src = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight());

            Rect target = new Rect();
            int textHeight = (int) getTextSize();
            target.left = 0;
            target.top = (int) (getMeasuredHeight() - getTextSize()) / 2 + 1;
            target.bottom = target.top + textHeight;
            target.right = (int) (textHeight * (bitmap.getWidth() / (float) bitmap.getHeight()));
            canvas.drawBitmap(bitmap, src, target, getPaint());
            canvas.translate(target.right + 2, 0);
        }

        super.onDraw(canvas);
    }
}
复制代码

 

结果:

%title插图%num

第三种方法,通过自定义attrs.xml来实现 ,通过context.obtainStyledAttributes(attrs,R.styleable.ImageTextView).getString()方法拿到属性值

(1)自定义一个attrs.xml文件

复制代码
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="ImageTextView">
        <attr name="iamgeDimension" format="dimension" />
        <attr name="imageReference" format="reference" />
        <attr name="iamgeColor" format="color" />
        <attr name="iamgeString" format="string" />
        <attr name="iamgeInteger" format="integer" />
        <attr name="iamgeFloat" format="float" />
        <attr name="iamgeBoolean" format="boolean" />
        <attr name="iamgeFraction" format="fraction" />
        <attr name="iamgeEnum1">
            <enum name="enum1" value="1"></enum>
            <enum name="enum2" value="2"></enum>
        </attr>
        <attr name="iamgeFlag">
            <flag name="flag1" value="1"></flag>
            <flag name="flag2" value="2"></flag>
            <flag name="flag3" value="3"></flag>
        </attr>
    </declare-styleable>
</resources>
复制代码

或者

复制代码
<?xml version="1.0" encoding="utf-8"?>
<resources>
  <attr name="iamgeDimension" format="dimension" />
        <attr name="imageReference" format="reference" />
        <attr name="iamgeColor" format="color" />
        <attr name="iamgeString" format="string" />
        <attr name="iamgeInteger" format="integer" />
        <attr name="iamgeFloat" format="float" />
        <attr name="iamgeBoolean" format="boolean" />
        <attr name="iamgeFraction" format="fraction" />
        <attr name="iamgeEnum1">
            <enum name="enum1" value="1"></enum>
            <enum name="enum2" value="2"></enum>
        </attr>
        <attr name="iamgeFlag">
            <flag name="flag1" value="1"></flag>
            <flag name="flag2" value="2"></flag>
            <flag name="flag3" value="3"></flag>
        </attr>

    <declare-styleable name="ImageTextView">
       <attr name="iamgeDimension"></attr>
        <attr name="imageReference"></attr>
        <attr name="iamgeColor"></attr>
        <attr name="iamgeString"></attr>
   <attr name="iamgeInteger"></attr>
        <attr name="iamgeFloat"></attr>
        <attr name="iamgeBoolean"></attr>
        <attr name="iamgeFraction"></attr>
   <attr name="iamgeEnum1"></attr>
        <attr name="iamgeFlag"></attr>
    </declare-styleable>
</resources>
复制代码

 

两种方法都可以,自定义属性分两步:

  1. 定义公共属性
  2. 定义控件的主题样式

如上面的xml文件*部分是公共的属性,第二部分是自定义控件MyCustomView的主题样式,该主题样式里的属性必须包含在公共属性里面。言外之意就是公共属性可以被多个自定义控件主题样式使用。

(2)在xml文件中使用这一属性,注意此时命名空间的书写规范。

 

复制代码
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:ImageTextView="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:gravity="center"
    tools:context=".MainActivity">

    <com.example.lenovo.custom_textview.ImageTextView3
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    ImageTextView:iamgeDimension="100dp"
    ImageTextView:imageReference="@drawable/trash"
    ImageTextView:iamgeColor="@color/material_blue_grey_800"
    ImageTextView:iamgeString="自定义属性"
    ImageTextView:iamgeInteger="100"
    ImageTextView:iamgeFloat="0.8"
    ImageTextView:iamgeBoolean="true"
    ImageTextView:iamgeFraction="200%p"
    ImageTextView:iamgeEnum1="enum2"
    ImageTextView:iamgeFlag="flag3"
    />
</LinearLayout>
复制代码

 

 

(3)在代码中使用context.obtainStyledAttributes获得属性值

<attr name="iamgeString" format="string" />

指 定为一个declare-styleable,而在declare-styleable 下的attr (即各属性)Android 的ADT 将会自动生成为declare-styleable的name 名字加上“_”加上对应attr(即属性名称)的名称,如上(ImageTextView_String)我们要得到Text 就需要R.styleable.ImageTextView_String,这一点的话可以看看R.java生成文件

 

复制代码
package com.example.lenovo.custom_textview;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.widget.TextView;

/**
 * Created by lenovo on 2016/2/18.
 */
public class ImageTextView3 extends TextView {
    public ImageTextView3(Context context) {
        super(context);
    }
    public ImageTextView3(Context context, AttributeSet attrs) {
        super(context, attrs);
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.ImageTextView);
  // TypedArray是存放资源的array,1.通过上下文得到这个数组,attrs是构造函数传进来的,对应attrs.xml  
   // 获得xml里定义的属性,格式为 名称_属性名 后面是默认值
        double iamgeDimension = typedArray.getDimension(R.styleable.ImageTextView_iamgeDimension, 0);
        int imageReference = typedArray.getResourceId(R.styleable.ImageTextView_imageReference, 0);
        bitmap = BitmapFactory.decodeResource(getResources(), imageReference);
        Drawable drawable = typedArray.getDrawable(R.styleable.ImageTextView_imageReference);
        double iamgeColor = typedArray.getColor(R.styleable.ImageTextView_iamgeColor, 0);
        String iamgeString = typedArray.getString(R.styleable.ImageTextView_iamgeString);
        double iamgeInteger = typedArray.getInteger(R.styleable.ImageTextView_iamgeInteger, 0);
        double iamgeFloat = typedArray.getFloat(R.styleable.ImageTextView_iamgeFloat, 0);
        boolean iamgeBoolean = typedArray.getBoolean(R.styleable.ImageTextView_iamgeBoolean, false);

//        ImageTextView:iamgeFraction="200%"
//        ImageTextView:iamgeFraction="200%p"
//         double iamgeFraction = typedArray.getFraction(R.styleable.ImageTextView_iamgeFraction, 4, 5, 1);
//        1)如果mageTextView_iamgeFraction 是200%,那么result就是:200%*4 ~ 8
//        2)如果mageTextView_iamgeFraction 是200%p,那么result就是:200%*5 ~ 10
        double iamgeFraction = typedArray.getFraction(R.styleable.ImageTextView_iamgeFraction, 4, 5, 1);
        double iamgeEnum1 = typedArray.getInteger(R.styleable.ImageTextView_iamgeEnum1, 0);
        double iamgeFlag = typedArray.getInteger(R.styleable.ImageTextView_iamgeFlag, 0);
   // 为了保持以后使用该属性一致性,返回一个绑定资源结束的信号给资源  
        typedArray.recycle(); //调用结束后务必调用recycle()方法,否则这次的设定会对下次的使用造成影响
        StringBuffer str = new StringBuffer();
        str.append("iamgeDimension=  " + iamgeDimension + "\n");
        str.append("imageReference=  " + imageReference + "\n");
        str.append("iamgeColor=  " + iamgeColor + "\n");
        str.append("iamgeBoolean=  " + iamgeBoolean + "\n");
        str.append("iamgeString=  " + iamgeString + "\n");
        str.append("iamgeInteger=  " + iamgeInteger + "\n");
        str.append("iamgeFloat=  " + iamgeFloat + "\n");
        str.append("iamgeFraction=  " + iamgeFraction + "\n");
        str.append("iamgeEnum1=  " + iamgeEnum1 + "\n");
        str.append("iamgeFlag=  " + iamgeFlag + "\n");
        setText(str.toString());

    }

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

    private Bitmap bitmap;

    @Override
    public void onDraw(Canvas canvas) {
        if (bitmap != null) {
            Rect src = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight());

            Rect target = new Rect();
            int textHeight = (int) getTextSize();
            target.left = 0;
            target.top = (int) (getMeasuredHeight() - getTextSize()) / 2 + 1;
            target.bottom = target.top + textHeight;
            target.right = (int) (textHeight * (bitmap.getWidth() / (float) bitmap.getHeight()));
            canvas.drawBitmap(bitmap, src, target, getPaint());
            canvas.translate(target.right + 2, 0);
        }

        super.onDraw(canvas);
    }
}
复制代码

结果:

%title插图%num

总结:

这是这两种为Android 注册 属性的使用方法,那么两者有什么区别呢?

在这里我认为起码有五点,大家可以找找看还有什么区别:

  • 第二种可以编译时报错,如果编程人员随便输入什么*种是不会报错的,第二种可以支持代码检测功能。
  • 第二种写法,跟Android 属性标准写法是一致的,而且可以统一书法规则。
  • 第二种写法,可以支持数据格式的验证,比如我们在attrs上注明只支持integer 那么就不可以使用字符串,这是*种达不到的。
  • 第二种写法,可以为VIEW提供选择操作,比如如上我们使用的ENUM让VIEW对应的属性支持ENUM列表,或者为其提供BOOL等只有双项选择的操作。
  • *种写法,所有的属性必须是引用自资源(不大确定,如果朋友有什么好的DEMO麻烦共享),第二种写法,可以即支持引用资源又可以直接输入做操作,为编程带来更多的方便性。

种种都说明,第二种写法更具规范性,功能更性,代码编写 也更优雅。

Android进阶笔记:AIDL内部实现详解

Android进阶笔记:AIDL内部实现详解 (二)

接着上一篇分析的aidl的流程解析。知道了aidl主要就是利用Ibinder来实现跨进程通信的。既然是通过对Binder各种方法的封装,那也可以不使用aidl自己通过Binder来实现跨进程通讯。那么这篇博客就主要就写一下通过上篇(Android进阶笔记:AIDL详解(一))总结的知识来自己实现跨进程通讯从而更加透彻的了解aidl的核心逻辑。

首先上一篇博客(Android进阶笔记:AIDL详解(一))中总结出一个结论————“onTransact方法是提供给server端用的,transact方法(内部类proxy封装了transact方法)和asInterface方法是给client端用的。”因此很清楚,只要我们在Server端实现跨进程需要调用的方法(类似aidl的接口实现)和onTransact方法,而服务端只要通过获得的IBinder对象来调用transact方法就可以代替aidl来实现跨进程通讯了。既然思路已经整理清楚了,那就一步一步来实现它。

Server端

首先Server端是要通过Service的onBind方法来给Client端一个Binder对象,那就先从这个Binder对象入手。那就先来创建了一个MyBinder类,代码如下:

MyBinder.java

public class MyBinder extends Binder {
    //标记方法的
    private static final int METHOD_ADD_CODE = 1001;
    //标识binder对象的
    private static final String DESCRIPTION = "not use aidl";

    @Override
    protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
        if (code == METHOD_ADD_CODE) {
            //验证一下binder
            data.enforceInterface(DESCRIPTION);
            //从parcel对象中读取参数
            int arg0 = data.readInt();
            int arg1 = data.readInt();
            //写入结果
            reply.writeInt(add(arg0, arg1));
            return true;
        }
        return super.onTransact(code, data, reply, flags);
    }

    private int add(int arg0, int arg1) {
        return arg0 + arg1;
    }
}

代码非常简单,只是重新写了一下onTransact方法。其实一共只有4步:

  1. 根据code的值来判断client端具体想要调用哪个方法;
  2. 读取parcel对象(data)中传入的参数;
  3. 调用自己本地的方法(add)并将参数传入;
  4. 把结果写入parcel对象(reply)中;

接着只要把这个自己定义的MyBinder类的实例通过Service.onBInder方法返回给Client端就可以了。

MyService.java

public class MyService extends Service {
    private MyBinder myBinder;

    public MyService() {
    }

    @Override
    public void onCreate() {
        super.onCreate();
        //创建实例
        myBinder = new MyBinder();
    }

    @Override
    public IBinder onBind(Intent intent) {
        //返回自定义的binder对象
        return myBinder;
    }
}

Client端

client端的代码无非就是把之前写在aidl中的proxy内部类的方法拿出来了。具体看代码:

WithoutAidlActivity.java

public class WithoutAidlActivity extends AppCompatActivity {

    private ServiceConnection serviceConnection;
    private IBinder binder;

    //以下两个参数要和server端保持一致
    //标记方法的(告知server端调用哪个方法)
    private static final int METHOD_ADD_CODE = 1001;
    //标识binder对象的
    private static final String DESCRIPTION = "not use aidl";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_without_aidl);
        serviceConnection = new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                Log.d("onServiceConnected", "onServiceConnected: connected success!");
                binder = service;
                //这里就代替aidl中的proxy来直接调用transact方法
                //先准备参数
                Parcel data = Parcel.obtain();
                Parcel reply = Parcel.obtain();
                data.writeInterfaceToken(DESCRIPTION);
                data.writeInt(123);
                data.writeInt(456);
                try {
                    //调用transact方法
                    binder.transact(METHOD_ADD_CODE, data, reply, 0);
                    //获得结果
                    int result = reply.readInt();
                    Log.d("onServiceConnected", "result = " + result);
                } catch (RemoteException e) {
                    e.printStackTrace();
                } finally {
                    data.recycle();
                    reply.recycle();
                }
            }

            @Override
            public void onServiceDisconnected(ComponentName name) {
                binder = null;
            }
        };

        bindService(new Intent("com.coder_f.aidlserver.MyService"), serviceConnection, BIND_AUTO_CREATE);

    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        unbindService(serviceConnection);
    }
}

首先连接成功后在serviceConnection.onServiceConnected方法中获得了IBinder实例,然后总共做了3个事情:

  1. 创建两个parcel对象分别存放参数(data)和返回值(reply)
  2. 调用transact方法,传入data,reply,和你要调用的方法code。*后的flag传入0表示有返回值(1表示没有又返回值)
  3. 从reply中获得结果

完成以上工作就可以不通过aidl实现跨进程通讯了。但是还是要说一下,这里我们server端调用的只是一个简单的add方法不耗时的,而transact方法则是在onServiceConnected方法中被调用的其实是在主线程中执行的。如果add方法换成一个耗时方法,那么主线程(UI线程)是会卡死的,调用transact方法时当前线程会被挂起知道结果被返回(有兴趣可以去试试,只要在add方法里面加一个Thread.sleep就可以了)。所以*好的办法就是起一个线程来调用transact方法。

友情链接: SITEMAP | 旋风加速器官网 | 旋风软件中心 | textarea | 黑洞加速器 | jiaohess | 老王加速器 | 烧饼哥加速器 | 小蓝鸟 | tiktok加速器 | 旋风加速度器 | 旋风加速 | quickq加速器 | 飞驰加速器 | 飞鸟加速器 | 狗急加速器 | hammer加速器 | trafficace | 原子加速器 | 葫芦加速器 | 麦旋风 | 油管加速器 | anycastly | INS加速器 | INS加速器免费版 | 免费vqn加速外网 | 旋风加速器 | 快橙加速器 | 啊哈加速器 | 迷雾通 | 优途加速器 | 海外播 | 坚果加速器 | 海外vqn加速 | 蘑菇加速器 | 毛豆加速器 | 接码平台 | 接码S | 西柚加速器 | 快柠檬加速器 | 黑洞加速 | falemon | 快橙加速器 | anycast加速器 | ibaidu | moneytreeblog | 坚果加速器 | 派币加速器 | 飞鸟加速器 | 毛豆APP | PIKPAK | 安卓vqn免费 | 一元机场加速器 | 一元机场 | 老王加速器 | 黑洞加速器 | 白石山 | 小牛加速器 | 黑洞加速 | 迷雾通官网 | 迷雾通 | 迷雾通加速器 | 十大免费加速神器 | 猎豹加速器 | 蚂蚁加速器 | 坚果加速器 | 黑洞加速 | 银河加速器 | 猎豹加速器 | 海鸥加速器 | 芒果加速器 | 小牛加速器 | 极光加速器 | 黑洞加速 | movabletype中文网 | 猎豹加速器官网 | 烧饼哥加速器官网 | 旋风加速器度器 | 哔咔漫画 | PicACG | 雷霆加速