zoukankan      html  css  js  c++  java
  • Android 高仿微信头像截取 打造不一样的自定义控件

    转载请表明出处:http://blog.csdn.net/lmj623565791/article/details/39761281,本文出自:【张鸿洋的博客】

    1、概述

    前面已经写了关于检测手势识别的文章,如果不了解可以参考:Android 手势检测实战 打造支持缩放平移的图片预览效果(下)。首先本篇文章,将对之前博客的ZoomImageView代码进行些许的修改与改善,然后用到我们的本篇博客中去,实现仿微信的头像截取功能,当然了,个人觉得微信的截取头像功能貌似做得不太好,本篇博客准备去其糟粕,取其精华;最后还会见识到不一样的自定义控件的方式,也是在本人博客中首次出现,如果有兴趣可以读完本篇博客,希望可以启到抛砖引玉的效果。

    2、效果分析

    1、效果图:

    我们来看看妹子的项链,嗯,妹子项链还是不错的~

    2、效果分析

    根据上面的效果,我们目测需要自定义两个控件,一个就是我们的可自由缩放移动的ImageView,一个就是那个白色的边框;然后一起放置到一个RelativeLayout中;最后对外公布一个裁剪的方法,返回一个Bitmap;

    暂时的分析就这样,下面我们来写代码~

    首先是白色框框那个自定义View,我们叫做ClipImageBorderView

    3、ClipImageBorderView

    分析下这个View,其实就是根据在屏幕中绘制一个正方形,正方形区域以外为半透明,绘制这个正方形需要与屏幕左右边距有个边距。

    我们准备按如下图绘制:

    按顺序在View的onDraw里面绘制上图中:1、2、3、4,四个半透明的区域,然后在中间正方形区域绘制一个正方形

    下面看下代码:

    [java] view plaincopy
     
    1. package com.zhy.view;  
    2.   
    3. import android.content.Context;  
    4. import android.graphics.Canvas;  
    5. import android.graphics.Color;  
    6. import android.graphics.Paint;  
    7. import android.graphics.Paint.Style;  
    8. import android.util.AttributeSet;  
    9. import android.util.TypedValue;  
    10. import android.view.View;  
    11. /** 
    12.  * @author zhy 
    13.  * 
    14.  */  
    15. public class ClipImageBorderView extends View  
    16. {  
    17.     /** 
    18.      * 水平方向与View的边距 
    19.      */  
    20.     private int mHorizontalPadding = 20;  
    21.     /** 
    22.      * 垂直方向与View的边距 
    23.      */  
    24.     private int mVerticalPadding;  
    25.     /** 
    26.      * 绘制的矩形的宽度 
    27.      */  
    28.     private int mWidth;  
    29.     /** 
    30.      * 边框的颜色,默认为白色 
    31.      */  
    32.     private int mBorderColor = Color.parseColor("#FFFFFF");  
    33.     /** 
    34.      * 边框的宽度 单位dp 
    35.      */  
    36.     private int mBorderWidth = 1;  
    37.   
    38.     private Paint mPaint;  
    39.   
    40.     public ClipImageBorderView(Context context)  
    41.     {  
    42.         this(context, null);  
    43.     }  
    44.   
    45.     public ClipImageBorderView(Context context, AttributeSet attrs)  
    46.     {  
    47.         this(context, attrs, 0);  
    48.     }  
    49.   
    50.     public ClipImageBorderView(Context context, AttributeSet attrs, int defStyle)  
    51.     {  
    52.         super(context, attrs, defStyle);  
    53.         // 计算padding的px  
    54.         mHorizontalPadding = (int) TypedValue.applyDimension(  
    55.                 TypedValue.COMPLEX_UNIT_DIP, mHorizontalPadding, getResources()  
    56.                         .getDisplayMetrics());  
    57.         mBorderWidth = (int) TypedValue.applyDimension(  
    58.                 TypedValue.COMPLEX_UNIT_DIP, mBorderWidth, getResources()  
    59.                         .getDisplayMetrics());  
    60.         mPaint = new Paint();  
    61.         mPaint.setAntiAlias(true);  
    62.     }  
    63.   
    64.     @Override  
    65.     protected void onDraw(Canvas canvas)  
    66.     {  
    67.         super.onDraw(canvas);  
    68.         //计算矩形区域的宽度  
    69.         mWidth = getWidth() - 2 * mHorizontalPadding;  
    70.         //计算距离屏幕垂直边界 的边距  
    71.         mVerticalPadding = (getHeight() - mWidth) / 2;  
    72.         mPaint.setColor(Color.parseColor("#aa000000"));  
    73.         mPaint.setStyle(Style.FILL);  
    74.         // 绘制左边1  
    75.         canvas.drawRect(0, 0, mHorizontalPadding, getHeight(), mPaint);  
    76.         // 绘制右边2  
    77.         canvas.drawRect(getWidth() - mHorizontalPadding, 0, getWidth(),  
    78.                 getHeight(), mPaint);  
    79.         // 绘制上边3  
    80.         canvas.drawRect(mHorizontalPadding, 0, getWidth() - mHorizontalPadding,  
    81.                 mVerticalPadding, mPaint);  
    82.         // 绘制下边4  
    83.         canvas.drawRect(mHorizontalPadding, getHeight() - mVerticalPadding,  
    84.                 getWidth() - mHorizontalPadding, getHeight(), mPaint);  
    85.         // 绘制外边框  
    86.         mPaint.setColor(mBorderColor);  
    87.         mPaint.setStrokeWidth(mBorderWidth);  
    88.         mPaint.setStyle(Style.STROKE);  
    89.         canvas.drawRect(mHorizontalPadding, mVerticalPadding, getWidth()  
    90.                 - mHorizontalPadding, getHeight() - mVerticalPadding, mPaint);  
    91.   
    92.     }  
    93.   
    94. }  

    我们直接预设了一个水平方向的边距,根据边距计算出正方形的边长,接下来就是按照上图分别会1、2、3、4四个区域,最后就是绘制我们的正方形~~

    代码还是很简单的~~我们的ClipImageBorderView就搞定了,我们决定来测试一下:

    布局文件:

    [html] view plaincopy
     
    1. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  
    2.     xmlns:tools="http://schemas.android.com/tools"  
    3.     android:layout_width="match_parent"  
    4.     android:layout_height="match_parent"  
    5.     android:background="@drawable/a"   >  
    6.   
    7.     <com.zhy.view.ClipImageBorderView  
    8.         android:id="@+id/id_clipImageLayout"  
    9.         android:layout_width="fill_parent"  
    10.         android:layout_height="fill_parent" />  
    11.   
    12. </RelativeLayout>  


    效果图:

    故意放了个背景,没撒用,就是为了能看出效果,可以看到我们的框框绘制的还是蛮不错的~~嗯,这个框框距离屏幕左右两侧的距离应该抽取出来,嗯,后面再说~

    4、ClipZoomImageView

    我们准备对我们原先的ZoomImageView进行简单的修改,修改的地方:
    1、在onGlobalLayout方法中,如果图片的宽或者高只要一个小于我们的正方形的边长,我们会直接把较小的尺寸放大至正方形的边长;如果图片的宽和高都大于我们的正方形的边长,我们仅仅把图片移动到我们屏幕的中央,不做缩放处理;

    2、根据步骤1,我们会获得初始的缩放比例(默认为1.0f),然后SCALE_MID , 与 SCALE_MAX 分别为2倍和4倍的初始化缩放比例。

    3、图片在移动过程中的边界检测完全根据正方形的区域,图片不会在移动过程中与正方形区域产生内边距

    4、对外公布一个裁切的方法

    部分代码:

    [java] view plaincopy
     
    1. /** 
    2.      * 水平方向与View的边距 
    3.      */  
    4.     private int mHorizontalPadding = 20;  
    5.     /** 
    6.      * 垂直方向与View的边距 
    7.      */  
    8.     private int mVerticalPadding;  
    9.   
    10.     @Override  
    11.     public void onGlobalLayout()  
    12.     {  
    13.         if (once)  
    14.         {  
    15.             Drawable d = getDrawable();  
    16.             if (d == null)  
    17.                 return;  
    18.             Log.e(TAG, d.getIntrinsicWidth() + " , " + d.getIntrinsicHeight());  
    19.             // 计算padding的px  
    20.             mHorizontalPadding = (int) TypedValue.applyDimension(  
    21.                     TypedValue.COMPLEX_UNIT_DIP, mHorizontalPadding,  
    22.                     getResources().getDisplayMetrics());  
    23.             // 垂直方向的边距  
    24.             mVerticalPadding = (getHeight() - (getWidth() - 2 * mHorizontalPadding)) / 2;  
    25.   
    26.             int width = getWidth();  
    27.             int height = getHeight();  
    28.             // 拿到图片的宽和高  
    29.             int dw = d.getIntrinsicWidth();  
    30.             int dh = d.getIntrinsicHeight();  
    31.             float scale = 1.0f;  
    32.             if (dw < getWidth() - mHorizontalPadding * 2  
    33.                     && dh > getHeight() - mVerticalPadding * 2)  
    34.             {  
    35.                 scale = (getWidth() * 1.0f - mHorizontalPadding * 2) / dw;  
    36.             }  
    37.   
    38.             if (dh < getHeight() - mVerticalPadding * 2  
    39.                     && dw > getWidth() - mHorizontalPadding * 2)  
    40.             {  
    41.                 scale = (getHeight() * 1.0f - mVerticalPadding * 2) / dh;  
    42.             }  
    43.   
    44.             if (dw < getWidth() - mHorizontalPadding * 2  
    45.                     && dh < getHeight() - mVerticalPadding * 2)  
    46.             {  
    47.                 float scaleW = (getWidth() * 1.0f - mHorizontalPadding * 2)  
    48.                         / dw;  
    49.                 float scaleH = (getHeight() * 1.0f - mVerticalPadding * 2) / dh;  
    50.                 scale = Math.max(scaleW, scaleH);  
    51.             }  
    52.   
    53.             initScale = scale;  
    54.             SCALE_MID = initScale * 2;  
    55.             SCALE_MAX = initScale * 4;  
    56.             Log.e(TAG, "initScale = " + initScale);  
    57.             mScaleMatrix.postTranslate((width - dw) / 2, (height - dh) / 2);  
    58.             mScaleMatrix.postScale(scale, scale, getWidth() / 2,  
    59.                     getHeight() / 2);  
    60.             // 图片移动至屏幕中心  
    61.             setImageMatrix(mScaleMatrix);  
    62.             once = false;  
    63.         }  
    64.   
    65.     }  
    66.   
    67.     /** 
    68.      * 剪切图片,返回剪切后的bitmap对象 
    69.      *  
    70.      * @return 
    71.      */  
    72.     public Bitmap clip()  
    73.     {  
    74.         Bitmap bitmap = Bitmap.createBitmap(getWidth(), getHeight(),  
    75.                 Bitmap.Config.ARGB_8888);  
    76.         Canvas canvas = new Canvas(bitmap);  
    77.         draw(canvas);  
    78.         return Bitmap.createBitmap(bitmap, mHorizontalPadding,  
    79.                 mVerticalPadding, getWidth() - 2 * mHorizontalPadding,  
    80.                 getWidth() - 2 * mHorizontalPadding);  
    81.     }  
    82.       
    83.     /** 
    84.      * 边界检测 
    85.      */  
    86.     private void checkBorder()  
    87.     {  
    88.   
    89.         RectF rect = getMatrixRectF();  
    90.         float deltaX = 0;  
    91.         float deltaY = 0;  
    92.   
    93.         int width = getWidth();  
    94.         int height = getHeight();  
    95.   
    96.         // 如果宽或高大于屏幕,则控制范围  
    97.         if (rect.width() >= width - 2 * mHorizontalPadding)  
    98.         {  
    99.             if (rect.left > mHorizontalPadding)  
    100.             {  
    101.                 deltaX = -rect.left + mHorizontalPadding;  
    102.             }  
    103.             if (rect.right < width - mHorizontalPadding)  
    104.             {  
    105.                 deltaX = width - mHorizontalPadding - rect.right;  
    106.             }  
    107.         }  
    108.         if (rect.height() >= height - 2 * mVerticalPadding)  
    109.         {  
    110.             if (rect.top > mVerticalPadding)  
    111.             {  
    112.                 deltaY = -rect.top + mVerticalPadding;  
    113.             }  
    114.             if (rect.bottom < height - mVerticalPadding)  
    115.             {  
    116.                 deltaY = height - mVerticalPadding - rect.bottom;  
    117.             }  
    118.         }  
    119.         mScaleMatrix.postTranslate(deltaX, deltaY);  
    120.   
    121.     }  


    这里贴出了改变的代码,完整的代码就不贴了,太长了,如果大家学习过前面的博客应该也会比较熟悉,若没有也没事,后面会提供源码。

    贴代码的目的,第一让大家看下我们改变了哪些;第二,我想暴露出我们代码中的问题,我们设置了一个这样的变量:mHorizontalPadding = 20;这个是手动和ClipImageBorderView里面的成员变量mHorizontalPadding 写的一致,也就是说这个变量,两个自定义的View都需要使用且需要相同的值,目前我们的做法,写死且每个View各自定义一个。这种做法不用说,肯定不好,即使抽取成自定义属性,两个View都需要进行抽取,且用户在使用的时候,还需要设置为一样的值,总觉得有点强人所难~~

    5、不一样的自定义控件

    现在我们考虑下:易用性。目前为止,其实我们的效果已经实现了,但是需要用户这么写布局文件:

    [html] view plaincopy
     
    1. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  
    2.     xmlns:tools="http://schemas.android.com/tools"  
    3.     android:layout_width="match_parent"  
    4.     android:layout_height="match_parent"  
    5.     android:background="#aaaaaa" >  
    6.   
    7.     <com.zhy.view.ZoomImageView  
    8.         android:id="@+id/id_zoomImageView"  
    9.         android:layout_width="fill_parent"  
    10.         android:layout_height="fill_parent"  
    11.         android:scaleType="matrix"  
    12.         android:src="@drawable/a" />  
    13.   
    14.     <com.zhy.view.ClipImageView  
    15.         android:layout_width="fill_parent"  
    16.         android:layout_height="fill_parent" />  
    17.   
    18. </RelativeLayout>  


    然后这两个类中都有一个mHorizontalPadding变量,且值一样,上面也说过,即使抽取成自定义变量,也需要在布局文件中每个View中各写一次。so, we need change . 这样的耦合度太夸张了,且使用起来蹩脚。

    于是乎,我决定把这两个控件想办法整到一起,用户使用时只需要声明一个控件:

    怎么做呢,我们使用组合的思想来自定义控件,我们再声明一个控件,继承子RelativeLayout,然后在这个自定义RelativeLayout中通过代码添加这两个自定义的布局,并且设置一些公用的属性,具体我们就开始行动。

    1、ClipImageLayout

    我们自定义一个RelativeLayout叫做ClipImageLayout,用于放置我们的两个自定义View,并且由ClipImageLayout进行设置边距,然后传给它内部的两个View,这样的话,跟用户交互的就一个ClipImageLayout,用户只需要设置一次边距即可。

    完整的ClipImageLayout代码:

    [java] view plaincopy
     
    1. package com.zhy.view;  
    2.   
    3. import android.content.Context;  
    4. import android.graphics.Bitmap;  
    5. import android.util.AttributeSet;  
    6. import android.util.TypedValue;  
    7. import android.widget.RelativeLayout;  
    8.   
    9. import com.zhy.clippic.R;  
    10. /** 
    11.  * zhy 
    12.  * @author zhy 
    13.  * 
    14.  */  
    15. public class ClipImageLayout extends RelativeLayout  
    16. {  
    17.   
    18.     private ClipZoomImageView mZoomImageView;  
    19.     private ClipImageBorderView mClipImageView;  
    20.   
    21.     /** 
    22.      * 这里测试,直接写死了大小,真正使用过程中,可以提取为自定义属性 
    23.      */  
    24.     private int mHorizontalPadding = 20;  
    25.   
    26.     public ClipImageLayout(Context context, AttributeSet attrs)  
    27.     {  
    28.         super(context, attrs);  
    29.   
    30.         mZoomImageView = new ClipZoomImageView(context);  
    31.         mClipImageView = new ClipImageBorderView(context);  
    32.   
    33.         android.view.ViewGroup.LayoutParams lp = new LayoutParams(  
    34.                 android.view.ViewGroup.LayoutParams.MATCH_PARENT,  
    35.                 android.view.ViewGroup.LayoutParams.MATCH_PARENT);  
    36.           
    37.         /** 
    38.          * 这里测试,直接写死了图片,真正使用过程中,可以提取为自定义属性 
    39.          */  
    40.         mZoomImageView.setImageDrawable(getResources().getDrawable(  
    41.                 R.drawable.a));  
    42.           
    43.         this.addView(mZoomImageView, lp);  
    44.         this.addView(mClipImageView, lp);  
    45.   
    46.           
    47.         // 计算padding的px  
    48.         mHorizontalPadding = (int) TypedValue.applyDimension(  
    49.                 TypedValue.COMPLEX_UNIT_DIP, mHorizontalPadding, getResources()  
    50.                         .getDisplayMetrics());  
    51.         mZoomImageView.setHorizontalPadding(mHorizontalPadding);  
    52.         mClipImageView.setHorizontalPadding(mHorizontalPadding);  
    53.     }  
    54.   
    55.     /** 
    56.      * 对外公布设置边距的方法,单位为dp 
    57.      *  
    58.      * @param mHorizontalPadding 
    59.      */  
    60.     public void setHorizontalPadding(int mHorizontalPadding)  
    61.     {  
    62.         this.mHorizontalPadding = mHorizontalPadding;  
    63.     }  
    64.   
    65.     /** 
    66.      * 裁切图片 
    67.      *  
    68.      * @return 
    69.      */  
    70.     public Bitmap clip()  
    71.     {  
    72.         return mZoomImageView.clip();  
    73.     }  
    74.   
    75. }  


    可以看到,现在用户需要使用头像裁切功能只需要声明下ClipImageLayout即可,完全避免了上述我们描述的问题,我们对用户屏蔽了两个真正实现的类。这个也是自定义控件的一种方式,希望可以借此抛砖引玉,大家能够更加合理的设计出自己的控件~~

    好了,我们的ClipImageLayout搞定以后,下面看下如何使用~

    6、用法

    1、布局文件

    [html] view plaincopy
     
    1. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  
    2.     xmlns:tools="http://schemas.android.com/tools"  
    3.     android:layout_width="match_parent"  
    4.     android:layout_height="match_parent"  
    5.     android:background="#aaaaaa"   >  
    6.   
    7.     <com.zhy.view.ClipImageLayout  
    8.         android:id="@+id/id_clipImageLayout"  
    9.         android:layout_width="fill_parent"  
    10.         android:layout_height="fill_parent" />  
    11.   
    12. </RelativeLayout>  

    2、MainActivity

    [java] view plaincopy
     
    1. package com.zhy.clippic;  
    2.   
    3. import java.io.ByteArrayOutputStream;  
    4.   
    5. import android.app.Activity;  
    6. import android.content.Intent;  
    7. import android.graphics.Bitmap;  
    8. import android.os.Bundle;  
    9. import android.view.Menu;  
    10. import android.view.MenuItem;  
    11.   
    12. import com.zhy.view.ClipImageLayout;  
    13.   
    14. public class MainActivity extends Activity  
    15. {  
    16.     private ClipImageLayout mClipImageLayout;  
    17.   
    18.     @Override  
    19.     protected void onCreate(Bundle savedInstanceState)  
    20.     {  
    21.         super.onCreate(savedInstanceState);  
    22.         setContentView(R.layout.activity_main);  
    23.   
    24.         mClipImageLayout = (ClipImageLayout) findViewById(R.id.id_clipImageLayout);  
    25.   
    26.     }  
    27.   
    28.     @Override  
    29.     public boolean onCreateOptionsMenu(Menu menu)  
    30.     {  
    31.         getMenuInflater().inflate(R.menu.main, menu);  
    32.         return true;  
    33.     }  
    34.   
    35.     @Override  
    36.     public boolean onOptionsItemSelected(MenuItem item)  
    37.     {  
    38.         switch (item.getItemId())  
    39.         {  
    40.         case R.id.id_action_clip:  
    41.             Bitmap bitmap = mClipImageLayout.clip();  
    42.               
    43.             ByteArrayOutputStream baos = new ByteArrayOutputStream();  
    44.             bitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos);  
    45.             byte[] datas = baos.toByteArray();  
    46.               
    47.             Intent intent = new Intent(this, ShowImageActivity.class);  
    48.             intent.putExtra("bitmap", datas);  
    49.             startActivity(intent);  
    50.   
    51.             break;  
    52.         }  
    53.         return super.onOptionsItemSelected(item);  
    54.     }  
    55. }  


    我们在menu里面体检了一个裁切的按钮,点击后把裁切好的图片传递给我们的ShowImageActivity

    看一下眼menu的xml

    [html] view plaincopy
     
    1. <menu xmlns:android="http://schemas.android.com/apk/res/android" >  
    2.   
    3.     <item  
    4.         android:id="@+id/id_action_clip"  
    5.         android:icon="@drawable/actionbar_clip_icon"  
    6.         android:showAsAction="always|withText"  
    7.         android:title="裁切"/>  
    8.   
    9. </menu>  

    3、ShowImageActivity

    [java] view plaincopy
     
    1. package com.zhy.clippic;  
    2.   
    3.   
    4. import android.app.Activity;  
    5. import android.graphics.Bitmap;  
    6. import android.graphics.BitmapFactory;  
    7. import android.os.Bundle;  
    8. import android.widget.ImageView;  
    9.   
    10.   
    11. public class ShowImageActivity extends Activity  
    12. {  
    13.     private ImageView mImageView;  
    14.   
    15.   
    16.     @Override  
    17.     protected void onCreate(Bundle savedInstanceState)  
    18.     {  
    19.         super.onCreate(savedInstanceState);  
    20.         setContentView(R.layout.show);  
    21.   
    22.   
    23.         mImageView = (ImageView) findViewById(R.id.id_showImage);  
    24.         byte[] b = getIntent().getByteArrayExtra("bitmap");  
    25.         Bitmap bitmap = BitmapFactory.decodeByteArray(b, 0, b.length);  
    26.         if (bitmap != null)  
    27.         {  
    28.             mImageView.setImageBitmap(bitmap);  
    29.         }  
    30.     }  
    31. }  

    layout/show.xml

    [html] view plaincopy
     
    1. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  
    2.     xmlns:tools="http://schemas.android.com/tools"  
    3.     android:layout_width="match_parent"  
    4.     android:layout_height="match_parent"  
    5.     android:background="#ffffff" >  
    6.   
    7.     <ImageView  
    8.         android:id="@+id/id_showImage"  
    9.         android:layout_width="wrap_content"  
    10.         android:layout_height="wrap_content"  
    11.         android:layout_centerInParent="true"  
    12.         android:src="@drawable/tbug"  
    13.          />  
    14.   
    15. </RelativeLayout>  

    好了,到此我们的 高仿微信头像截取功能 就已经结束了~~希望大家可以从本篇博客中可以领悟到something~

    最后我们把ClipImageLayout里面的mHorizontalPadding设置为50,贴个静态效果图~

    ok ~~

    源码点击下载

  • 相关阅读:
    Cognitive Radio Cognitive Network Simulator (NS3 based)
    RCU(Read-Copy Update)synchronize原理分析
    英雄黑客:一个试图提高物联网安全性的“义务警员”
    Android NDK放弃GCC,全面转向Clang
    Why is FreeBSD deprecating GCC in favor of Clang/LLVM?
    (Low Level Virtual Machine) LLVM 与 Clang介绍
    RMS谈GCC、LLVM和Copyleft
    LLVM和Clang背后的故事
    FEP: 测试 lineage, 获得 CPU, MEMORY 统计信息
    以wifi-example-sim.cc为例说明NS3统计数据模型
  • 原文地址:https://www.cnblogs.com/dongweiq/p/4261984.html
Copyright © 2011-2022 走看看