未知账号

明天的一切都是未知的!


  • 首页

  • 归档

  • 标签

按照特定的某种方式对一个数组里面的数值排序

发表于 2017-09-08

Question

  • 有一个数组,例如是{1,3,2,4,6,7,5,9},排序后数组为{9,7,4,3,1,2,6,5},请找出该数组的排序规律并用代码实现排序!

Answer

  • 首先说说基本思路

    首先看两组数据的变换规律,它是以第一个数字为中心变换,找到数组的长度len,将第一个数字设置在下标为len/2的位置,然后,用两个变量,left = len/2 - 1,right = len/2 + 1; 按照原有数组的顺序,依次赋值给num[left]并left–,num[right]并right–,直至原有数组所有的数字都被操作完。。。结果正确了,但是你会发现,空间复杂度是真的高。。。所以我们需要想想,怎么优化。

  • 再升级一下思路

    那怎么优化呢,看来主要是空间复杂度上面进行优化了,对,还记得插入排序的思路没?对,就是按照那个思路,首先找到第一个数字要放置的位置len/2,设置一个变量tmp, 再用for循环遍历,从第二个值即num[1]开始,依次遍历,如果是编号i为奇数的,那么从0到编号i,tmp = num[i], 从0到i-1每个位置依次后移一个位置,最后将num[0] = tmp, 如果编号i为奇数,则不管它,那么就只需要 一个变量,但是我发现好像时间复杂度变长了,得套用两个for循环。

  • 以下是两种思路的代码实现

方式一

static void fun(int[] num){
    int len = num.length;
    int[] data = new int[len + 1];
    int tmpLoc = len / 2;
    data[tmpLoc] = num[0];
    int left = tmpLoc - 1,right = tmpLoc + 1;
    for(int i = 1; i < len; i ++){
        data[left--] = num[i];
        if (i < len - 1){
            i ++;
        }
        data[right] = num[i];
        if (right < len){
            right ++;
        }
    }

    for(int i = 0; i < len; i ++){
        System.out.print(data[i] + " ");
    }
}

方式二

static void fun2(int[] num){
    int len  = num.length;
    int tmp;
    for (int i = 1; i < len; i ++){
        tmp = num[i];
        if (i % 2 != 0){
            for (int j = i; j > 0; j --){
                num[j] = num[j - 1];

            }
            num[0] = tmp;
        }

    }

    for(int i = 0; i < len; i ++){
        System.out.print(num[i] + " ");
    }
}
  • 以上就是两个思路以及两种解决方案的代码啦。。突然间发现,方法二不加 i % 2 != 0 的条件,就可以反转数组啦。。。哈哈哈!新大陆呀

用递归实现n的阶乘以及n的求和,还有A的n次方(n可负可正)。

发表于 2017-09-08

什么是递归算法?

这是个人理解,如有问题,还请多多指教。。。递归就是按照找到一个退出递归的出口,其余的部分一直按照某种规律执行,自己调用自己的方式执行代码。

递归的优势?

这也是个人理解,如有问题,还请多多指教。。。递归其实就是把一个大问题化成一个小问题,大事化小,小事化了的方式,而且递归与for循环相比的话,他是可以记录中间过程值的,每一次后一步调用都能直接获取前一步的值,如果以后再哪个环节想要使用的话是可以直接使用的,不需要再次进行计算。

用递归实现以上两算法题。

  • 实现从0-n的加法:

    描述思路:加法很简单啦,前面所有数量的和加上自己即可啦,一般这种情况只有正数;

代码实现:

static int sum(int n){
    if (n == 0)
        return 0;
    if (n == 1)
        return 1;
    return sum(n - 1) + n;
    // 如果分正负
    //if(n > 0){
    //    return sum(n - 1) + n;
    //} else{
    //    return sum(n + 1) + n;
    //}

}
  • 实现整数n的阶乘:

    首先描述一下思路,整数的阶乘,那么意味着整数分为正整数和负整数,那么我们需要分两种情况讨论,当n是正数的时候,f(n) = n f(n -1), 但是当n为负数的时候f(n) = n f(n + 1);

看看代码实现

static int f(int n){
    if (n == 0){
        return 1;
    } else if (n > 0){
        return f(n - 1) * n;
    } else if (n < 0){
        return f(n + 1) * n;
    }
    return -1;
}
  • 实现数字A的n次方(n 可负可正)

    首先描述一下大众思路,在印象里面,A的多少次方就是多少个A相乘啊,那如果n是负数呢?那就是绝对值个A相乘,最后取倒数。

下面看看普通实现方式的代码

static double accelarateN(int a, int num){
    if (a == 0)
        return 0;
    if (num >= 0){
        return bZ(a, num);
    } else if (num < 0){
       // num = Math.abs(num);
       // return 1 / bZ(a, num);
        return 1 / sZ(a, num);
    }
    return -1;
}

static double bZ(int a, int n){
    if (n == 0){
        return 1;
    }
    return a * bZ(a, n -1);
}

static double sZ(int b, int n){
    if (n == 0)
        return 1;
    return b * sZ(b, n + 1);
}

其实看完上面的代码,会发现,这样就可以实现功能了,但是,我们可以想一想,如何更好的实现这个算法,求一个数的整数次方,那么很明显,这里面有个重复的过程,那就是一个数的N次方等于这个数的(N/2)次方的平方,那么代码就可如下写:

static double accPower(int base, int exponent){
    double result = 1;
    int n = exponent;
    exponent = Math.abs(exponent);
    if (exponent == 0)
        return 1;
    if (exponent == 1)
        return base;
    result *= accPower(base, exponent >> 1) * accPower(base, exponent >> 1);
    if ((exponent & 1) == 1){
        result = base * result;
    }

    if (n < 0){
        result = 1 / result;
    }
    return result;
}

怎么样,结果是一样的,但是运算速度快了很多啦。。。每次都以次方的形式递减。。。

单链表反转

发表于 2017-09-08

Question

  • Answer

  • 首先说说基本思路
  • 代码实现

布局优化

发表于 2017-08-29

布局优化之include

  • 如果一个布局会在很多地方都被用到,那么我们可以使用这个标签,新建一个XML文件,假设叫my_include_layout,在其他布局使用,代码如下

    <include
    android:id="@+id/my_include_layout"
    layout="@layout/my_include_layout" />
    

    但是在写的时候有以下几个地方需要注意:

    1. 如果我们在其他应用的地方对我们的标签进行了命名,那么在标签里面的命名将失效即被调用者的命名覆盖
    2. 如果我们想要对标签进行权重比例分配,那么权重标签是必须定义在标签实现里面的,调用的地方是没有这个属性的。
    3. 标签里面的实现就是我们普通的布局文件,普通布局文件拥有的属性标签文件都有

布局优化之merge

  • merge标签主要是为了减少布局层级的,常见的主要有以下几种使用情况。

    1. 如果一个布局的根布局的层级属性是FrameLayout,那么,我们可以直接略去,使用merge标签对布局文件进行书写。这样可以减少层级
    2. 如果一个布局文件的外部布局是LinearLayout,内部布局又是一个Linearlayout,那么,内部的布局文件再加这个标签其实就是多余,这时候我们就可以使用merge标签,在XML文件中调用的时候就采用include同样的方式。

布局优化之ViewStub

  • ViewStub标签其实也就是懒加载标签模式,由于ViewStub其实是个宽高都为0的一个View,默认值是看不见的,所以只有每次需要的时候使用setVisibility 或者 Inflate 的时候才会加载,使用这个标签不是影响UI初始化的性能。使用ViewStub标签承载的对象一般是那种不常用的布局文件,比如进度条,显示错误消息等,使用这个标签可以减少内存使用量,加快渲染进度。标签使用样式如下:

    <ViewStub  
        android:id="@+id/stub_import"  
        android:inflatedId="@+id/stub_comm_lv"  
        android:layout="@layout/my_comment_layout"  
        android:layout_width="fill_parent"  
        android:layout_height="wrap_content"  
        android:layout_gravity="bottom" />  
    

当我们想加载布局时,有两种方法,同时也对应两种判断一个布局文件是否已经被加载的方法,代码示例如下:

//方式一:
ViewStub listStub = (ViewStub) findViewById(R.id.stub_import);  
// 加载评论列表布局  
listStub.setVisibility(View.VISIBLE);  
// 获取到评论ListView,注意这里是通过ViewStub的inflatedId来获取  
    ListView commLv = findViewById(R.id.stub_comm_lv);  
        if ( listStub.getVisibility() == View.VISIBLE ) {  
               // 已经加载, 否则还没有加载  
        }  
    }  
   } 

//方式二:
ViewStub listStub2 = (ViewStub) findViewById(R.id.stub_import) ;  
    // 成员变量commLv2为空则代表未加载  
    if ( commLv2 == null ) {  
    // 加载评论列表布局, 并且获取评论ListView,inflate函数直接返回ListView对象  
      commLv2 = (ListView)listStub2.inflate();  
    } else {  
    // ViewStub已经加载  
    }  

ViewStub使用有几个需要注意的地方:

  1. iewStub也有一个缺陷,那就是不支持merge属性

  2. 判断是否已经加载过, 如果通过setVisibility来加载,那么通过判断可见性即可;如果通过inflate()来加载是不可以通过判断可见性来处理的,而需要使用方式2来进行判断。

  3. findViewById的问题,注意ViewStub中是否设置了inflatedId,如果设置了则需要通过inflatedId来查找目标布局的根元素。

Activity的生命周期里的onStart和onStop

发表于 2017-08-28

Activity的生命周期里的onStart与onResume的区别

  • onStart函数的相关代码:

    /**
     * Called after {@link #onCreate} &mdash; or after {@link #onRestart} when
     * the activity had been stopped, but is now again being displayed to the
     * user.  It will be followed by {@link #onResume}.
     *
     * <p><em>Derived classes must call through to the super class's
     * implementation of this method.  If they do not, an exception will be
     * thrown.</em></p>
     *
     * @see #onCreate
     * @see #onStop
     * @see #onResume
     */
    protected void onStart() {
        if (DEBUG_LIFECYCLE) Slog.v(TAG, "onStart " + this);
        mCalled = true;
    
        if (!mLoadersStarted) {
            mLoadersStarted = true;
            if (mLoaderManager != null) {
                mLoaderManager.doStart();
            } else if (!mCheckedForLoaderManager) {
                mLoaderManager = getLoaderManager("(root)", mLoadersStarted, false);
            }
            mCheckedForLoaderManager = true;
        }
    
        getApplication().dispatchActivityStarted(this);
    }
    
  • onResume函数的相关代码

    /**
     * Called after {@link #onRestoreInstanceState}, {@link #onRestart}, or
     * {@link #onPause}, for your activity to start interacting with the user.
     * This is a good place to begin animations, open exclusive-access devices
     * (such as the camera), etc.
     *
     * <p>Keep in mind that onResume is not the best indicator that your activity
     * is visible to the user; a system window such as the keyguard may be in
     * front.  Use {@link #onWindowFocusChanged} to know for certain that your
     * activity is visible to the user (for example, to resume a game).
     *
     * <p><em>Derived classes must call through to the super class's
     * implementation of this method.  If they do not, an exception will be
     * thrown.</em></p>
     *
     * @see #onRestoreInstanceState
     * @see #onRestart
     * @see #onPostResume
     * @see #onPause
     */
    protected void onResume() {
        if (DEBUG_LIFECYCLE) Slog.v(TAG, "onResume " + this);
        getApplication().dispatchActivityResumed(this);
        mActivityTransitionState.onResume();
        mCalled = true;
    }
    

    从注释就可以看出来,onResume是执行在onStart函数之后的,onStart函数是执行在onCreate函数之后或者执行了onStop函数之后重新打开界面执行,onResume则是在执行了onStart或者onRestart或者onPause函数之后执行,在里面可以进行很多初始化工作,比如开始动画等等。。。他俩最大的不同,则是onStart是可见但用户不可对界面进行交互性操作的时刻,但是onResume则是不仅可见,也可与界面进行交互,并且界面会作出相应反应。

Activity的生命周期里的onStop与onPause的区别

  • onStop函数的相关代码:

    /**
     * Called when you are no longer visible to the user.  You will next
     * receive either {@link #onRestart}, {@link #onDestroy}, or nothing,
     * depending on later user activity.
     *
     * <p>Note that this method may never be called, in low memory situations
     * where the system does not have enough memory to keep your activity's
     * process running after its {@link #onPause} method is called.
     *
     * <p><em>Derived classes must call through to the super class's
     * implementation of this method.  If they do not, an exception will be
     * thrown.</em></p>
     *
     * @see #onRestart
     * @see #onResume
     * @see #onSaveInstanceState
     * @see #onDestroy
     */
    protected void onStop() {
        if (DEBUG_LIFECYCLE) Slog.v(TAG, "onStop " + this);
        if (mActionBar != null) mActionBar.setShowHideAnimationEnabled(false);
        mActivityTransitionState.onStop();
        getApplication().dispatchActivityStopped(this);
        mTranslucentCallback = null;
        mCalled = true;
    }
    
  • onPause函数的相关代码:

    /**
     * Called as part of the activity lifecycle when an activity is going into
     * the background, but has not (yet) been killed.  The counterpart to
     * {@link #onResume}.
     *
     * <p>When activity B is launched in front of activity A, this callback will
     * be invoked on A.  B will not be created until A's {@link #onPause} returns,
     * so be sure to not do anything lengthy here.
     *
     * <p>This callback is mostly used for saving any persistent state the
     * activity is editing, to present a "edit in place" model to the user and
     * making sure nothing is lost if there are not enough resources to start
     * the new activity without first killing this one.  This is also a good
     * place to do things like stop animations and other things that consume a
     * noticeable amount of CPU in order to make the switch to the next activity
     * as fast as possible, or to close resources that are exclusive access
     * such as the camera.
     *
     * <p>In situations where the system needs more memory it may kill paused
     * processes to reclaim resources.  Because of this, you should be sure
     * that all of your state is saved by the time you return from
     * this function.  In general {@link #onSaveInstanceState} is used to save
     * per-instance state in the activity and this method is used to store
     * global persistent data (in content providers, files, etc.)
     *
     * <p>After receiving this call you will usually receive a following call
     * to {@link #onStop} (after the next activity has been resumed and
     * displayed), however in some cases there will be a direct call back to
     * {@link #onResume} without going through the stopped state.
     *
     * <p><em>Derived classes must call through to the super class's
     * implementation of this method.  If they do not, an exception will be
     * thrown.</em></p>
     *
     * @see #onResume
     * @see #onSaveInstanceState
     * @see #onStop
     */
    protected void onPause() {
        if (DEBUG_LIFECYCLE) Slog.v(TAG, "onPause " + this);
        getApplication().dispatchActivityPaused(this);
        mCalled = true;
        }
    

    同样根据注释可以看出,onStop是在界面不再对用户可见的时候执行,而且在内存不足而导致系统无法保留此进程的情况下,onStop() 可能都不会被执行,但是可以执行一些耗时操作,因为是后台进程,不会对APP运行产生较大的影响;onPause也是在界面不可见的时候执行,但他就像onResume一样,与之对应,是执行动画取消,耗时或者耗费CPU的动作取消的好时机,但是不能够执行过于耗时的操作,因为要保证能快速完好的加载下一个界面。

HV相关的ViewSever的基本使用方法

发表于 2017-08-28

HV 设置使用方法,注释是添加代码的位置

// onCreate()
ViewServer.get(this).addWindow(this);
//onResume()
ViewServer.get(this).setFocusedWindow(this);
//onDestroy()
ViewServer.get(this).removeWindow(this);

添加依赖

allprojects {
    repositories {
        maven { url 'https://jitpack.io' }
    }
}

 dependencies {
   //在HV上可以看见自己的View布局的一个开源库
    compile 'com.github.romainguy:ViewServer:-SNAPSHOT'
  }

Android的通知栏

发表于 2017-08-28

自定义CircleImageView的实现

发表于 2017-08-27

首先来说说我最近遇到的奇葩事!

上了一周的火,患了牙周炎,吃药一直不好,不吃药了,居然好了,哈哈。。。

其次,就来说说为啥想写这篇博客?

好吧,因为面试的时候被问到了,问得自己哑口无言的,那么,为了增加自己的知识储备量,我需要动手实践一下啦。。。为了下次被问到的时候不再尴尬。。。也为了自己以后实现这类需求的时候能够信手拈来啊。。。

然后呢?我们就来说说实现思路啦!

其实,这个实现是有个开源项目的,那么,我们也就是一个学习的过程啦,学习过程,当然还是要记录一下的,方便以后自己回顾啊。。。下面我就说说自己依样画葫芦的过程以及过程中遇到的问题。

那么,大概的实现思路是什么呢?其实我有想到俩,第一个,很简单,就是在一个ImageView上面绘制一个遮罩,遮罩设置成和图片一样大小,但是只有中间的最大圆显示出来遮罩下面的东西,其余的地方都绘制成和承载背景一个颜色,那么就能看起来是个圆形图片啦。第二个,我们就只用绘制一层,避免过度绘制,就是把我们图片的承托工具绘制成圆形的,再把图片根据承托背景调整,最终显示,这样绘制出来的效果会比第一个好,既然说了这是按照开源项目依样画葫芦,那么肯定也按照他们的思路,用好的那个办法啦。。。下面说说“copy”过程。。。。

  • 首先,就像很大自定义View一样,新建一个类,我们命名为CircleImageView,并实现前三个构造函数,然后呢,在我们attrs文件里面,添加我们可能会用到的基本属性啦。

    <declare-styleable name="CircleImageView">
       <attr name="border_width" format="dimension"/>
       <attr name="border_color" format="color"/>
       <attr name="border_overlay" format="boolean"/>
    </declare-styleable>
    
  • 其次呢?我们就像自定义View一样啊,定义我们需要用到的一些变量。

    private static final int COLOR_DRAWABLE_DIMENSION = 2;
    private static final int DEFAULT_BORDER_WIDTH = 0;
    private static final int DEFAULT_BORDER_COLOR = Color.BLACK;
    private static final ScaleType SCALE_TYPE = ScaleType.CENTER_CROP;
    private static final Bitmap.Config BITMAP_CONFIG = Bitmap.Config.ARGB_8888;
    private static final boolean DEFAULT_BORDER_OVERLAY = false;
    private final RectF mDrawableRect = new RectF();
    private final RectF mBorderRect = new RectF();
    
    private final Matrix mShaderMatrix = new Matrix();
    private int mBorderWidth = DEFAULT_BORDER_WIDTH;
    private int mBorderColor = DEFAULT_BORDER_COLOR;
    
    private Paint mBitmapPaint = new Paint();
    private Paint mBorderPaint = new Paint();
    
    private Bitmap mBitmap;
    private BitmapShader mBitmapShader;
    private int mBitmapWidth;
    private int mBitmapHeight;
    
    private float mDrawableRadius;
    private float mBorderRadius;
    
    private ColorFilter mColodrFilter;
    
    private boolean mReady;
    private boolean mSetUpPending;
    private boolean mBorderOverlay;
    
  • 然后呢?在构造函数里面获取自定义参数。

    public CircleImageView(Context context) {
        super(context);
        init();
    }
    
    public CircleImageView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }
    
    public CircleImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CircleImageView, defStyleAttr, 0);
        mBorderWidth = typedArray.getDimensionPixelSize(R.styleable.CircleImageView_border_width, DEFAULT_BORDER_WIDTH);
        mBorderColor = typedArray.getColor(R.styleable.CircleImageView_border_color, DEFAULT_BORDER_COLOR);
        mBorderOverlay = typedArray.getBoolean(R.styleable.CircleImageView_border_overlay, DEFAULT_BORDER_OVERLAY);
    
        typedArray.recycle();
    
        init();
    }
    

    等一下,init函数是什么?干什么用的呢?我们先看下代码。

    private void init(){
        super.setScaleType(SCALE_TYPE);
        mReady = true;
    
        if (mSetUpPending){
            setUp();
            mSetUpPending = false;
        }
    }
    

    仔细研究发现,好像是有个变量初始化,还有个根据变量看是否执行setUp函数的,作用就是保证第一次执行setup函数里下面代码要在构造函数执行完毕时调用,那么,setUp函数又是干什么的呢?一连串的问号???继续看代码。

    private void setUp(){
        if (!mReady){
            mSetUpPending = true;
            return;
        }
    
        if (mBitmap == null){
            return;
        }
    
        // 构建渲染器,用mBitmap来填充绘制区域 ,参数值代表如果图片太小的话 就直接拉伸
        mBitmapShader = new BitmapShader(mBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
        mBitmapPaint.setAntiAlias(true);
        // 设置图片画笔渲染器
        mBitmapPaint.setShader(mBitmapShader);
        mBorderPaint.setStyle(Paint.Style.STROKE);
        mBorderPaint.setAntiAlias(true);
        mBorderPaint.setColor(mBorderColor);
        mBorderPaint.setStrokeWidth(mBorderWidth);
    
        mBitmapHeight = mBitmap.getHeight();
        mBitmapWidth = mBitmap.getWidth();
    
        mBorderRect.set(0, 0, getWidth(), getHeight());
        //计算 圆形带边界部分(外圆)的最小半径,取mBorderRect的宽高减去一个边缘大小的一半的较小值(这个地方我比较纳闷为什么求外圆半径需要先减去一个边缘大小)
        mBorderRadius = Math.min((mBorderRect.height() - mBorderWidth) / 2, (mBorderRect.width() - mBorderWidth) / 2);
         // 初始图片显示区域为mBorderRect(CircleImageView的布局实际大小)
        mDrawableRect.set(mBorderRect);
        if (!mBorderOverlay){
            //通过inset方法  使得图片显示的区域从mBorderRect大小上下左右内移边界的宽度形成区域
            mDrawableRect.inset(mBorderWidth, mBorderWidth);
        }
        //这里计算的是内圆的最小半径,也即去除边界宽度的半径
        mDrawableRadius = Math.min(mDrawableRect.height() / 2, mDrawableRect.width() / 2);
        //设置渲染器的变换矩阵也即是mBitmap用何种缩放形式填充
        updateShaderMatrix();
        //手动触发ondraw()函数 完成最终的绘制
        invalidate();
    }
    

    哦,原来是对代码自定义属性进行取值的,还有就是构建渲染器BitmapShader用Bitmap来填充绘制区域,设置样式和内外圆半径计算等,以及调用updateShaderMatrix()函数和 invalidate()函数;那么,updateShaderMatrix函数又是干什么的呢?一看名字就知道啦,肯定和Shader,Matrix 有关啦,对,他就是拿来计算缩放比例和平移位置,以及设置BitmapShader的Matrix 参数的。下面展示代码:

    private void updateShaderMatrix(){
        float scale;
        float dx = 0;
        float dy = 0;
    
        mShaderMatrix.set(null);
        // 找出宽高中较小的缩放比例 
        if (mBitmapWidth * mDrawableRect.height() > mDrawableRect.width() * mBitmapHeight){
            scale = mDrawableRect.height() / (float)mBitmapHeight;
            dx = (mDrawableRect.height() - mBitmapHeight * scale) * 0.5f;
        } else {
            scale = mDrawableRect.width() / (float) mBorderWidth;
            dy = (mDrawableRect.height() - mBorderWidth * scale) * 0.5f;
        }
    
        // 缩放
        mShaderMatrix.setScale(scale, scale);
        //平移
        mShaderMatrix.postTranslate((int) (dx + 0.5f) + mDrawableRect.left, (int)(dy + 0.5f) + mDrawableRect.top);
        //设置变换矩阵
        mBitmapShader.setLocalMatrix(mShaderMatrix);
    }
    
  • 再然后呢?想象一下,ImageView类的自定义控件,肯定是显示图片是主要部分啦,但是显示图片分为很多种类,根据BitMap,资源ID显示,根据Drawable显示,根据URL显示等。目前我们先实现这四种吧。。

     @Override
    public void setImageBitmap(Bitmap bm) {
        super.setImageBitmap(bm);
        mBitmap = bm;
        setUp();
    }
    
    @Override
    public void setImageDrawable(@Nullable Drawable drawable) {
        super.setImageDrawable(drawable);
        mBitmap = getBitmapFromDrawable(drawable);
        setUp();
    }
    
    @Override
    public void setImageResource(@DrawableRes int resId) {
        super.setImageResource(resId);
        mBitmap = getBitmapFromDrawable(getDrawable());
        setUp();
    }
    
    @Override
    public void setImageURI(Uri uri) {
        super.setImageURI(uri);
        mBitmap = getBitmapFromDrawable(getDrawable());
        setup();
    }
    

    看了以上代码以后,又有疑惑了,getBitmapFromDrawable 函数又是什么?呀,这就是个将Drawable转换为Bitmap的函数啦,代码实现如下。。。

    private Bitmap getBitmapFromDrawable(Drawable drawable){
        if (drawable == null){
            return null;
        }
    
        if (drawable instanceof BitmapDrawable){
            return ((BitmapDrawable) drawable).getBitmap();
        }
    
        Bitmap bitmap;
        if (drawable instanceof ColorDrawable){
            bitmap = Bitmap.createBitmap(COLOR_DRAWABLE_DIMENSION, COLOR_DRAWABLE_DIMENSION, BITMAP_CONFIG);
        } else {
            bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), BITMAP_CONFIG);
        }
    
        Canvas canvas = new Canvas(bitmap);
        drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
         //这一步很重要,将图片绘制到画布上
        drawable.draw(canvas);
        return bitmap;
    }
    
  • 对了,千万记住,别忘了还有最重要的一步哟,那就是重写OnDraw函数啦,那onDraw函数里面到底实现了些什么呢?当然就是绘制啦,show time:

    @Override
    protected void onDraw(Canvas canvas) {
        if (getDrawable() == null){
            return;
        }
    
        canvas.drawCircle(getWidth() / 2, getHeight() / 2, mDrawableRadius, mBitmapPaint);
        if (mBorderWidth != 0){
            canvas.drawCircle(getWidth() / 2, getHeight() / 2, mBorderRadius, mBorderPaint);
        }
    }
    

    做完了以上几个步骤,那么剩下的,就是一些细枝末节的补充工作啦。。完整代码就不贴出了,我主要是介绍一下思路,完整代码Google一下,GitHub上有开源原项目代码哦。。。嘻嘻。。。

说完思路啦,最后呢?我们就来总结总结啦。。。

  • 就如你们看到的一样,自定义的View,不论是ImageView还是TextView,都逃不过这几个步骤啦。
    1. 自定义View的属性
    2. 继承View,重写构造函数,并在构造函数里获取我们的自定义属性
    3. 重写onMeasure方法(由于这个我们需要控制显示大小以及形状,所以是要重写的,一般的自定义View不需要实现)
    4. 重写OnLayout方法(这一次我们没有用到,因为我们的布局很简单,没有强制设置该图片要定位在布局中的某个位置,不然,我们也需要实现的哟)
    5. 重写onDraw方法。。。这一步,一般都要有的,毕竟,我们自定义view就是需要显示我们想要显示的内容啊,那么如何显示呢,肯定是需要先有内容再显示,内容怎么来呢?绘制。。。所以,这一步基本上是必备的哟。。小伙伴们一定记住哟。。。

总结完啦,希望自己每周都能对自己用到的新东西做个总结,坚持学习,坚持实践,每天进步一点点。。。(^__^) 嘻嘻……

自定义TextView的实现

发表于 2017-08-26

说说需求

  • 自定义View是在Android开发中经常遇到的一种最基本功能的实现,那么,我们要如何实现一个自定义的TextView呢?这个TextView 包含两个功能,第一个是可以自定义字体颜色,大小等基本属性,第二个是根据不同的输入内容,折行居中显示。那么下面,我么就来一一实现以上两个基本功能。

说说实现过程

  1. 第一步呢,新建一个命名为AutoLinefeedTextView的类继承自View,实现他的基本构造方法。
  2. 第二步呢,在我们项目工程的/res/values/文件夹下查看是否有一个attrs 文件,若没有,则新建,否则直接打开编辑。在里面添加我们需要的自定义属性,我们在新建TextView的时候,由于我们要达到可以简单方便的在我们的布局文件中修改我们的文字内容,字体大小和字体颜色,所以,我们在自定义的时候也就需要自定义这些属性,首先对我们自定义的这个属性命名,这里我们为了方便使用,就和自定义View的名称保持一致,也命名为AutoLinefeedTextView,然后再对属性进行子属性的format定义,在自定义的format属性里,可以有很多种类,比如string,color,dimension,boolean等等,而我们自定义TextView的时候用到的很少,代码如下:

    <declare-styleable name="AutoLinefeedTextView">
        <attr name="mText" format="string"/>
        <attr name="mTextColor" format="color"/>
        <attr name="mTextSize" format="dimension"/>
    </declare-styleable>
    
  3. 完成上两步的准备工作之后,我们就要干正事啦,我们都知道,自定义View的很多时候呢,我们都要重写View里面的onDraw()方法,当然,这次也不例外。但是呢,在onDraw方法内部,我们会用到很多内容,比如画笔,比如文字,那么,onDraw函数是个经常被调用的函数,为了防止发生一直新建对象没有及时销毁导致OOM,我们在onDraw之前呢,就要首先定义好我们所需要用到的一些基本属性,那么,我们在哪里定义呢,当然是构造函数里面啦。。。接下来,我们就来初始化我们所需要用到的一些工具,我们可以创建一个叫init()的函数,方便我们在构造函数里面调用,当然,也可以构造函数之间相互调用,把初始化函数放在最终调用的构造函数里。由于在Android自定义属性的构造函数有多个,那么我们到底需要实现几个呢,一般情况下只需要实现三个,就是包含1,2,3个参数的三个,因为含有4个参数的构造函数是在Android5.1之后加进去的,我们很多时候可以不需要,那么这一次呢,我们也不实现,采用第二种方式对我们的所需对象进行初始化。

    3.1 首先呢,我们对我们所可能需要用到的东西进行声明,我们在脑海里面思考一下,自定义view我们需要用到些什么呢,和attrs里面一样,肯定有文字内容,文字大小,文字颜色,然后呢,我们要写字,,肯定需要笔啊,那么我们就还有一直画笔,然后呢,我们要写字,我们要控制文字写在哪里,呈现在什么位置,那我们肯定有个容器来承托这些内容啊,对,我们还需要一个Rect,那我们还需要什么呢,思考良久,要求是还要自动换行,怎么自动换行呢,哦,我们需要把文字内容根据屏幕大小分成一个list,然后依次显示在每一行,那么接下来,我们就开始show代码啦。

    private Rect mBound;
    private Paint mPaint;
    private int mTextSize;
    private int mTextColor;
    private String mTextStr;
    private List<String> mStrList;
    

    3.2 其次呢,我们就需要在我们的构造函数里对这些东西进行初始化啦,记得一点,我们的文字,文字大小和文字颜色我们是在自定义属性里面添加了自定义属性的,那么,我们要如何使用呢?接下来,就是我们的show time

    public AutoLinefeedTextView(Context context) {
        this(context, null);
    }
    
    public AutoLinefeedTextView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }
    
    public AutoLinefeedTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    
        //  初始化我们刚才思考良久才想出来的List
        mStrList = new ArrayList<>();
        //别走神,这儿是关键,如何获取我们的自定义属性
        TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.AutoLinefeedTextView, defStyleAttr, 0);
        //初始化我们的文字
        mTextStr = typedArray.getString(R.styleable.AutoLinefeedTextView_mText);
        //初始化我们的文字颜色,前一个参数为自定义的,如果我们在XML文件里面没有添加怎么办,没事,我们还有第二个参数,默认显示的颜色
        mTextColor = typedArray.getColor(R.styleable.AutoLinefeedTextView_mTextColor, Color.BLACK);
        //初始化我们的文字大小,和文字原理类似
        mTextSize = typedArray.getDimensionPixelSize(R.styleable.AutoLinefeedTextView_mTextSize, 100);
        //用完之后呢,方便下次使用,我们得回收一下下啦
        typedArray.recycle();
        // 对画笔进行初始化
        mPaint = new Paint();
        mPaint.setTextSize(mTextSize);
        mPaint.setColor(mTextColor);
        //依托承载对象的工具
        mBound = new Rect();
        mPaint.getTextBounds(mTextStr, 0, mTextStr.length(), mBound);
    }
    

    3.3 再其次,正如前面所说,我们需要重写View的OnDraw函数,又由于我们需要居中显示,我们还得重写onMeasure函数,如果对onMeasure里面的MeasureSpec.EXACTLY不太懂的话呢,记得百度哦,很多博客哒,哈哈,这里假设我们都懂。再次show time。

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
    
        canvas.drawText(mText, getWidth() / 2 - mBound.width() / 2, getHeight() / 2 + mBound.height() / 2, mPaint);
    }
    
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
    
        int width;
        int height;
        if (widthMode == MeasureSpec.EXACTLY){
            width = widthSize;
        } else {
            float textWidth = mBound.width();
            width = (int) (getPaddingLeft() + textWidth + getPaddingRight());
        }
    
        if (heightMode == MeasureSpec.EXACTLY){
            height = heightSize;
        } else {
            float textHeight = mBound.height();
            height = (int) (getPaddingTop() + textHeight + getPaddingBottom());
        }
    
        setMeasuredDimension(width, height);
    }
    

    3.4 完成以后,我们发现,诶,并没有用到我们的list啊,对,为什么呢?因为我们这个方式的显示时不正常的啦,我们会看到东西只有一行展示且显示不全,那么接下来,我们将要分析一下,如何折行显示并显示完全。。。想想看,我们应该按照怎样的逻辑把我们的一堆文字拆分成一个列表呢,思考一下我们会发现,我们的文字内容是一定的,我们的承托工具宽度是一定的,那么刚好,分成几行,那不就是显示完所有文字所需要的宽度/承托工具的宽度的行数么?可是,除不尽怎么办?没事,向上取整就好啦,这里呢,我们采取另一个办法,我们看我们的除法发生后,获取的值是否包含小数点,如果不包含,那么代表刚好整除,结果就是行数,如果包含,那么我们选择小数点前面的数字+1的方式作为行数。。。毕竟我们需要把所有东西都显示出来啊。。。知道了多少行,那么每一行显示些什么内容呢?难道文字那么乖乖听话自己就换行啦?当然不可能。那么我们如何拆分文字呢,那就要用到我们超级强大的subString函数啦,我们在知道函数以后,采用一个循环,把我们的文字拆分成一个列表装进我们刚才已经定义好的list里面,但是还有一点需要谨记,我们拆分一次之后,原字符串也得subString哦,为什么呢,因为前面的文字已经被显示出来啦,不然一直重复显示,就达不到完全显示的效果啦。。思路总结完毕,那么接下开又是我们的show time。睁大双眼,别眨眼哦

    boolean isOpenLines = true;//这是用来判断是否需要折行显示的变量
    float lineNum;
    float splineNum;
    
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
    
        float textWidth = mBound.width();
        if (mStrList.size() == 0){
            int padding = getPaddingLeft() + getPaddingRight();
            int specWidth = widthSize - padding;
            if (textWidth < specWidth){
                lineNum = 1;
                mStrList.add(mTextStr);
            } else {
                isOpenLines = false;
                splineNum = textWidth / specWidth;
                String splineNumStr = splineNum + "";
                if (splineNumStr.contains(".")){
                    lineNum = Integer.parseInt(splineNumStr.substring(0, splineNumStr.indexOf("."))) + 1;
                } else {
                    lineNum = splineNum;
                }
                int lineLength = (int)(mTextStr.length() / splineNum);
                for (int i = 0; i < lineNum; i ++){
                    String lineStr;
                    if (mTextStr.length() < lineLength){
                        lineStr = mTextStr.substring(0, mTextStr.length());
                    } else {
                        lineStr = mTextStr.substring(0, lineLength);
                    }
    
                    mStrList.add(lineStr);
    
                    if (!TextUtils.isEmpty(lineStr)){
                        if (mTextStr.length() < lineLength){
                            mTextStr = mTextStr.substring(0, mTextStr.length());
                        } else {
                            mTextStr = mTextStr.substring(0, lineLength);
                        }
                    } else {
                        break;
                    }
                }
            }
        }
    
        int width;
        int height;
    
        if (widthMode == MeasureSpec.EXACTLY){
            width = widthSize;
        } else {
            if (isOpenLines){
                width = (int) (getPaddingLeft() + textWidth + getPaddingRight());
            } else {
                width = widthSize;
            }
        }
    
        if (heightMode == MeasureSpec.EXACTLY){
            height = heightSize;
        } else {
            float textHeight = mBound.height();
            if (isOpenLines){
                height = (int) (getPaddingTop() + textHeight + getPaddingBottom());
            } else {
                height = (int) (getPaddingTop() + textHeight * lineNum + getPaddingBottom());
            }
        }
    
        setMeasuredDimension(width, height);
    }
    

    3.5 当然,我们的onMeasure完成改造之后,我们的onDraw也不能落下啊。

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
    
        for (int i = 0; i < mStrList.size(); i ++){
            mPaint.getTextBounds(mStrList.get(i), 0, mStrList.get(i).length(), mBound);
            canvas.drawText(mStrList.get(i), (getWidth() / 2 - mBound.width() / 2), (getPaddingTop() + (mBound.height() * i)), mPaint);
        }
    }
    

    3.6 以上几个步骤,就完成了我们的自定义布局的显示啦,那么剩下的最后一步,大家肯定都会的哟,就是在我们需要用到我们自定义样式的布局的位置,把它加进去哟。。。完成以后,就大功告成啦。。。。开不开心,激不激动,简不简单。。。O(∩_∩)O哈哈哈~

说说感想

  • 以前一直觉得自定义布局好难啊,根本不知道从何下手,亲自实践过后发现,也就那么回事嘛,多动手,多实践,实践出真知这句话,在哪里都适用。。。回过头来想一想,自定义TextView不就是三个那么几个小步骤么?首先新建一个类,然后在attrs下建立一个自己需要的自定义内容,再然后在我们的构造函数里对我们所需要用到的东西初始化,然后如果内容需要绘制,就重写我们的onDraw函数,如果我们的布局大小需要计算整合,就再重写我们的onMeasure函数,如果还有,我们的布局位置需要改变,那么我们就再重写我们的onLayout函数。。。完成以上几步,那么我们的自定义textview也就基本实现啦。。。。万事开头难,开始了一切就简单啦。。。

仿微信右滑返回功能

发表于 2017-08-25

首先新建两个activity

  • MainActivity

    MainActivity 只需要在onCreate方法中实现向第二个activity的跳转即可。关键代码如下:

    Intent intent = new Intent(this, TargetActivity.class);
    startActivity(intent);
    
  • TargetActivity

    TargetActivity 即是我们的目标activity,需要执行我们想要实现的各类方法

  • 通过思考,我们知道,我们要挪动的activity其实就是挪动activity的依托界面,也就是支撑整个子View的DecorView,所以,我们首先要获取到界面的DecorView,然后,在滑动过程中,整个手势监听是最重要的,熟知事件传递的小伙伴知道,要监听手势活动,在Activity里面就要实现OnTouchEvent方法,那么滑动到什么位置的时候界面关闭,什么位置保持不变呢,这就需要我们用变量控制我们手势挪动的距离了,所以,我们用变量和我们屏幕宽度进行对比,暂时定为挪动距离 < 屏幕的1/2宽度,targetActivity不被finish,否则,finish。下面即是代码实现。
  1. 首先定义我们在滑动过程中会用到的对比变量

    View decorView;
    float downX;
    float screenWidth, screenHeight;
    
  2. 然后在onCreate 函数里面实现对以上变量进行初始化

    DisplayMetrics metrics = new DisplayMetrics();
    getWindowManager().getDefaultDisplay().getMetrics(metrics);
    screenWidth = metrics.widthPixels;
    screenHeight = metrics.heightPixels;
    
  3. 复写onTouchEvent方法,控制界面的展示与消失,但是为了使界面消失和还原的过程不那么生硬,我们采用两个动画的方式,使加载变缓和。

    3.1 还原动画

    private void moveBackToLeft(float moveDistanceX){
        ObjectAnimator.ofFloat(decorView, "X", moveDistanceX, 0).setDuration(300).start();
    }
    

    3.2 关闭动作动画

        private void continueMoveToRight(float moveDistanceX){
            // 定义动画
            ValueAnimator animator = ValueAnimator.ofFloat(moveDistanceX, screenWidth);
            animator.setDuration(300);
            animator.start();
    
            //添加动画过程监听
            animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
               public void onAnimationUpdate(ValueAnimator animation) {
                float x = (float)animation.getAnimatedValue();
                decorView.setX(x);
            }
        });
    
        //添加动画完成动作监听,其中SimpleAnimationListener是个自己实现了Animator.AnimatorListener的abstract类
        animator.addListener(new SimpleAnimationListener()    {
                @Override
                public void onAnimationEnd(Animator animation) {
                finish();
            }
        });
    }
    

    3.3 复写onTouchEvent方法。

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (event.getAction() == MotionEvent.ACTION_DOWN){
            downX = event.getX();
    
        } else if (event.getAction() == MotionEvent.ACTION_MOVE){
            float moveDistanceX = event.getX() - downX;
    
            if (moveDistanceX > 0){
                decorView.setX(moveDistanceX);
            }
    
        } else if (event.getAction() == MotionEvent.ACTION_UP){
            float moveDistanceX = event.getX() - downX;
    
            if (moveDistanceX > screenWidth / 2){
                continueMoveToRight(moveDistanceX);
            } else {
                moveBackToLeft(moveDistanceX);
            }
        }
        return super.onTouchEvent(event);
    }
    
  4. 实现以上方法之后,诶,你会发现,界面好像是在挪动,可是呢,挪走之后的部分依然是原布局的颜色,看不到底层布局,这时候,我们就应该设置我们TargetActivity的属性啦。

设置我们的TargetActivity属性

1 打开我们res/values/styles 文件,在里面新建一个自定义的属性,如下所示,我们命名为SuspendTheme

<style name="SuspendTheme" parent="@android:style/Theme.Black.NoTitleBar.Fullscreen">
    <item name="android:windowBackground">@android:color/transparent</item>
    <item name="android:windowIsTranslucent">true</item>
    <item name="android:colorBackgroundCacheHint">@null</item>
</style> 

2 打开我们activity对应的module下的AndroidManifest文件,对我们的TargetActivity进行我们自定义的属性配置

<activity android:name=".present.SuspendActivity"
        android:theme="@style/SuspendTheme"/>

再次运行整个app,你会发现,右滑返回的基本功能实现啦,那么接下来,我们要实现的,就是在右滑过程的底层activity界面的透明度渐变展示啦。下一次我将继续总结,如何更完美的实现仿微信的右滑返回。

12…4
HM

HM

懂的or不懂的,我都记录在这里

36 日志
3 标签
GitHub Twitter Weibo DouBan ZhiHu FriendLink
© 2017 HM
由 Hexo 强力驱动
主题 - NexT.Muse