精华内容
参与话题
问答
  • 请问下各位大神,公司打算要在特定机型上开发一款app,app要用到系统的一些包 ,但是开发时怎么调用特定机的方法和类啊
  • android开发定制ViewPager滑动事件

    千次阅读 2015-08-21 21:13:17
    我在viewpager+fragment学习笔记中简单介绍过ViewPager+Fragment的用法,其实并不难,当时实现了一个如下图所示的效果:然而,在实际开发中这一点技术可能根本不够用,项目中会有各种各样奇葩的需求,我最近就遇到了...

    明天还要加班,苦逼的程序猿,简单说说最近遇到的一个问题吧。
    我在viewpager+fragment学习笔记中简单介绍过ViewPager+Fragment的用法,其实并不难,当时实现了一个如下图所示的效果:


    这里写图片描述


    然而,在实际开发中这一点技术可能根本不够用,项目中会有各种各样奇葩的需求,我最近就遇到了一个怪异的需求,捣鼓之后还是解决了,今天和大家聊聊。

    由于涉及到公司项目,我在这里就使用我自己制作的一个Demo来和大家介绍。
    我们要实现的效果如下图:


    这里写图片描述


    这里一共有三个Fragment,其中两个通过左右滑动显示,第三个要通过点击右上角的+来显示,在第三个Fragment上我们通过点击Fragment上的一个按钮再回到第一个Fragment上。其实这个要做非常简单,就是重写ViewPager的onTouch事件,在onTouch中判断当前的item,如果是1则取消向左滑动的动作。大致的一个思路就是这样。看代码:

    public class MyViewPager extends ViewPager {
    
        private int startX;
        private float currentX;
    
        public MyViewPager(Context context) {
            super(context);
        }
    
        public MyViewPager(Context context, AttributeSet attrs) {
            super(context, attrs);
        }
        @Override
        public boolean onTouchEvent(MotionEvent ev) {
            switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                startX = (int) ev.getX();
                Log.i("lenve", "ACTION_DOWN");
                break;
            case MotionEvent.ACTION_MOVE:
                Log.i("lenve", "ACTION_MOVE");
                currentX = ev.getX();
                break;
            case MotionEvent.ACTION_UP:
                Log.i("lenve", "ACTION_UP");
                break;
            }
            if (getCurrentItem() == 1 && currentX < startX) {
                return false;
            } else if (getCurrentItem() == 2) {
                return false;
            } else {
                return super.onTouchEvent(ev);
            }
        }
    }

    我在项目中是重写了onInterceptTouchEvent方法。但是这里重写onInterceptTouchEvent不管用,还是得重写onTouchEvent事件,个中原因想到周日好好研究研究,明天还要加班,今天就说这么多吧。有问题欢迎讨论。

    源码下载

    展开全文
  • Android开发定制自己的日志工具

    千次阅读 2016-08-28 14:26:40
    相信很多的Android开发人员都知道使用Android打印日志的工具LogCat,虽然Android中自带的日志工具功能非常强大,但也不能说是完成没有缺点,例如在打印日志的控制方面就做得不够好。 打个比方,你正在
    • 今天分享的东西是以前看过的Android大神郭霖写的《第一行代码》一书中的一个小的章节。和打印日志信息有关,但是很适合开发中使用,大家可以好好的利用起来;下面就是书中的内容。

    • 相信很多的Android开发人员都知道使用Android打印日志的工具LogCat,虽然Android中自带的日志工具功能非常强大,但也不能说是完成没有缺点,例如在打印日志的控制方面就做得不够好。

    • 打个比方,你正在编写一个比较庞大的项目,期间为了方便调试,在代码的很多地方都打印了大量的日志。最近项目已经基本完成了,但是却有一个非常让人头疼的问题,之前用于调试的那些日志,在项目正常上线后仍会照常打印,这样不仅会降低程序的运行效率,还有可能将一些机密性的数据泄漏出去。

    • 那该怎么办呢,难道要一行一行把所有打印日志的代码都删除?显然这不是什么好点子,不仅费时费力,而且以后你继续维护这个项目的时候可能还会需要这些日志。因此,最理想的情况是能够自由地控制日志的打印,当程序处于开发阶段就让日志打印出来,当程序上线了之后就把日志屏蔽掉。


    看起来好像是挺高级的一个功能,其实并不复杂,我们只需要定制一个自己的日志工具就可以轻松完成了。比如新建一个LogUtil类,代码如下所示:

    package com.example.imageloadertest;
    
    import android.util.Log;
    
    /**
     * 自定义的日志打印工具类
     * @author 邹奇
     *
     */
    public class LogUtil {
    
        /**
         * 定义6个静态常量,用来表示日志信息的打印等级
         * 由1到5打印等级依次升高
         */
        public static final int VERBOSE = 1;
        public static final int DEBUG = 2;
        public static final int INFO = 3;
        public static final int WARN = 4;
        public static final int ERROR = 5;
    
        public static final int NOTHING = 6;
    
        /**
         * 该静态常量的值用来控制你想打印的日志等级;
         * 比如当前LEVEL的值为常量1(VERBOSE),那么你以上5个日志等级都是可以打印的;
         * 假如当前LEVEL的值为常量2(DEBUG),那么你只能打印从DEBUG(2)到ERROR(5)之间的日志信息;
         * 假如你要是不想让日志信息打印出现,那么将LEVEL的值置为NOTHING即可。
         */
        public static final int LEVEL = VERBOSE;
    
        // 调用Log.v()方法打印日志
        public static void v(String tag, String msg){
            if(LEVEL <= VERBOSE){
                /**
                 * 加了一个日志信息值的判断,为了防止参数msg的值为null情况的出现
                 */
                if(msg != null){
                    Log.v(tag, msg);
                }else {
                    Log.v(tag, "is NULL");
                }
            }
        }
    
        // 调用Log.d()方法打印日志
        public static void d(String tag, String msg){
            if(LEVEL <= DEBUG){
                if(msg != null){
                    Log.d(tag, msg);
                }else {
                    Log.d(tag, "is NULL");
                }
            }
        }
    
        // 调用Log.i()方法打印日志
        public static void i(String tag, String msg){
            if(LEVEL <= INFO){
                if(msg != null){
                    Log.i(tag, msg);
                }else {
                    Log.i(tag, "is NULL");
                }
            }
        }
    
        // 调用Log.w()方法打印日志
        public static void w(String tag, String msg){
            if(LEVEL <= WARN){
                if(msg != null){
                    Log.w(tag, msg);
                }else {
                    Log.w(tag, "is NULL");
                }
            }
        }
    
        // 调用Log.e()方法打印日志
        public static void e(String tag, String msg){
            if(LEVEL <= ERROR){
                if(msg != null){
                    Log.e(tag, msg);
                }else {
                    Log.e(tag, "is NULL");
                }
            }
        }
    
    }
    

    从上面的代码可以看到,我有和原文不一样的地方,就是加了一个参数msg值为null的判断,防止日志信息值为null的出现。还有就是我加了一些注释,可以帮助大家更好的理解这个工具类的作用。


    • 可以看到,我们在LogUtil中先是定义了VERBOSE、DEBUG、INFO、WARN、ERROR、NOTHING这六个整型常量,并且它们对应的值都是递增的。然后又定义了一个LEVEL常量,可以将它的值指定为上面六个常量中的任意一个。

    • 接下来我们提供了v()、d()、i()、w()、e()这五个自定义的日志方法,在其内部分别调用了Log.v()、Log.d()、Log.i()、Log.w()、Log.e()这五个方法来打印日志,只不过在这些自定义的方法中我们都加入了一个if()判断,只有当LEVEL常量的值小于或等于对应日志级别值的时候,才会将日志打印出来。

    • 这样就把一个自定义的日志工具创建好了,之后在项目里我们可以像使用普通的日志工具一样使用LogUtil,比如打印一行DEBUG级别的日志就可以这样写:

      LogUtil.d("TAG", "This is debug info.");

      打印一行WARN级别的日志就可以这样写:

      LogUtil.w("TAG", "This is warn info.");

    • 然后我们只需要修改LEVEL常量的值,就可以自由地控制日志的打印行为了。比如让LEVEL等于VERBOSE就可以把所有的日志都打印出来,让LEVEL等于WARN就可以只打印警告以上级别的日志,让LEVEL等于NOTHING就可以把所有日志都屏蔽掉。

    • 使用了这种方法之后,刚才所说的那个问题就不复存在了,你只需要在开发阶段将LEVEL指定成VERBOSE,当项目正式上线的时候将LEVEL指定成NOTHING就可以了。


    每天进步一点点!加油!

    展开全文
  • 总觉得在上篇博文里面对在ContextImpl里面注册系统服务的过程讲的很乱,这里再写一点,说明一下注册的过程。

    总觉得在上篇博文里面对在ContextImpl里面注册系统服务的过程讲的很乱,这里再写一点,说明一下注册的过程。
    注册的核心是一个类:ServiceFetcher。ServiceFetcher的定义如下:

    static class ServiceFetcher {
        int mContextCacheIndex = -1;
    
        /**
         * Main entrypoint; only override if you don't need caching.
         */
        public Object getService(ContextImpl ctx) {
            ArrayList<Object> cache = ctx.mServiceCache;
            Object service;
            synchronized (cache) {
                if (cache.size() == 0) {
                    // Initialize the cache vector on first access.
                    // At this point sNextPerContextServiceCacheIndex
                    // is the number of potential services that are
                    // cached per-Context.
                    for (int i = 0; i < sNextPerContextServiceCacheIndex; i++) {
                        cache.add(null);
                    }
                } else {
                    service = cache.get(mContextCacheIndex);
                    if (service != null) {
                        return service;
                    }
                }
                service = createService(ctx);
                cache.set(mContextCacheIndex, service);
                return service;
            }
        }
    
        /**
         * Override this to create a new per-Context instance of the
         * service.  getService() will handle locking and caching.
         */
        public Object createService(ContextImpl ctx) {
            throw new RuntimeException("Not implemented");
        }
    }

    可见,这个类没有字段,只有两个函数,一个get和一个create。所以,可以把这个类当作一个Service的生产工厂,当你创建这样一个工厂的时候,你可以设置这个工厂生产什么样的服务,即重载createService方法。

    再来看注册服务的代码:

    registerService(IFLYTEK_RADIO_MANAGER_SERVICE, new ServiceFetcher() {
        public Object createService(ContextImpl ctx) {
            IBinder b = ServiceManager.getService(IFLYTEK_RADIO_MANAGER_SERVICE);
            return new android.os.iflytek.RadioManager(ctx, IRadioManager.Stub.asInterface(b));
        }});

    registerService方法有两个参数,第一个参数就是服务的名称,第二个参数就是新建的一个ServiceFetcher对象。这个过程可以这么理解,为了生产服务,我新建了一个工厂(ServiceFetcher),然后我告诉了工厂怎么生产这个对象(重载createService()方法)。这样,当我需要服务的时候,工厂就能够生产我需要的服务(通过createService()方法)。

    展开全文
  • Android开发技巧——定制仿微信图片裁剪控件

    万次阅读 多人点赞 2016-03-08 16:57:50
    拍照——裁剪,或者是选择图片——裁剪,是我们设置头像或上传图片时经常需要的一组操作。上篇讲了Camera的使用,这篇讲一下我对图片裁剪的实现。...在Android中,裁剪图片的控件库还是挺多的,特别是github上比较流行

    拍照——裁剪,或者是选择图片——裁剪,是我们设置头像或上传图片时经常需要的一组操作。上篇讲了Camera的使用,这篇讲一下我对图片裁剪的实现。

    背景

    1. 下面的需求都来自产品。
    2. 裁剪图片要像微信那样,拖动和放大的是图片,裁剪框不动。
    3. 裁剪框外的内容要有半透明黑色遮罩。
    4. 裁剪框下面要显示一行提示文字(这点我至今还是持保留意见的)。

    在Android中,裁剪图片的控件库还是挺多的,特别是github上比较流行的几个,都已经进化到比较稳定的阶段,但比较遗憾的是它们的裁剪过程是拖动或缩放裁剪框,于是只好自己再找,看有没有现成或半成品的轮子,可以不必从零开始。
    踏破铁鞋无觅处,皇天不负苦心人。我终于找到了两篇博客:《Android高仿微信头像裁剪》《Android 高仿微信头像截取 打造不一样的自定义控件》,以及csdn上找到的前面博客所对应的一份代码,并最终实现了自己的裁剪控件。

    大神的实现过程

    首先先了解一下上面的高仿微信裁剪控件的实现过程。说起来也不难,主要是下面几点:
    1,重写ImageView,并监听手势事件,包括双点,两点缩放,拖动,使它成为一个实现缩放拖动图片功能的控件。
    2,定义一个Matrix成员变量,对于维护该图片的缩放、平移等矩阵数据。
    3,拖动或缩放时,图片与裁剪框的相交面积一定与裁剪框相等。即图片不能拖离裁剪框。
    3,在设置图片时,先根据图片的大小进行初始化的缩放平移操作,使得上面第三条的条件下图片尽可能的小。
    4,每次接收到相对应的手势事件,都进行对应的矩阵计算,并将计算结果通过ImageViewsetImageMatrix方法应用到图片上。
    5,裁剪框是一个单独的控件,与ImageView同样大,叠加到它上面显示出来。
    6,用一个XXXLayout把裁剪框和缩放封装起来。
    7,裁剪时,先创建一个空的Bitmap并用其创建一个Canvas,把缩放平移后的图片画到这个Bitmap上,并创建在裁剪框内的Bitmap(通过调用Bitmap.createBitmap方法)。

    我的定制内容

    我拿到的代码是鸿洋大神版本之后再被改动的,代码上有点乱(虽然功能上是实现的裁剪)。在原有的功能上,我希望进行的改动有:

    • 合并裁剪框的内容到ImageView中
    • 裁剪框可以是任意长宽比的矩形
    • 裁剪框的左右外边距可以设置
    • 遮罩层颜色可以设置
    • 裁剪框下有提示文字(自己的产品需求)
    • 后面产品又加入了一条裁剪图片的最大大小

    属性定义

    在上面的功能需求中,我定义了以下属性:

    <declare-styleable name="ClipImageView">
        <attr name="civHeight" format="integer"/>
        <attr name="civWidth" format="integer"/>
        <attr name="civTipText" format="string"/>
        <attr name="civTipTextSize" format="dimension"/>
        <attr name="civMaskColor" format="color"/>
        <attr name="civClipPadding" format="dimension"/>
    </declare-styleable>

    其中:

    • civHeightcivWidth是裁剪框的宽高比例。
    • civTipText提示文字的内容
    • civTipTextSize提示文字的大小
    • civMaskColor遮罩层的颜色值
    • civClipPadding裁剪内边距。由于裁剪框是在控件内部的,最终我选择使用padding来说明裁剪框与我们控件边缘的距离。

    成员变量

    成员变量我进行了一些改动,把原本用于定义裁剪框的水平边距变量及其他没什么用的变量等给去掉了,并加入了自己的一些成员变量,最终如下:

        private final int mMaskColor;//遮罩层颜色
    
        private final Paint mPaint;//画笔
        private final int mWidth;//裁剪框宽的大小(从属性上读到的整型值)
        private final int mHeight;//裁剪框高的大小(同上)
        private final String mTipText;//提示文字
        private final int mClipPadding;//裁剪框相对于控件的内边距
    
        private float mScaleMax = 4.0f;//图片最大缩放大小
        private float mScaleMin = 2.0f;//图片最小缩放大小
    
        /**
         * 初始化时的缩放比例
         */
        private float mInitScale = 1.0f;
    
        /**
         * 用于存放矩阵
         */
        private final float[] mMatrixValues = new float[9];
    
        /**
         * 缩放的手势检查
         */
        private ScaleGestureDetector mScaleGestureDetector = null;
        private final Matrix mScaleMatrix = new Matrix();
    
        /**
         * 用于双击
         */
        private GestureDetector mGestureDetector;
        private boolean isAutoScale;
    
        private float mLastX;
        private float mLastY;
    
        private boolean isCanDrag;
        private int lastPointerCount;
    
        private Rect mClipBorder = new Rect();//裁剪框
        private int mMaxOutputWidth = 0;//裁剪后的图片的最大输出宽度

    构造方法

    构造方法里主要是多了一些我们自定义属性的读取:

    
        public ClipImageView(Context context) {
            this(context, null);
        }
    
        public ClipImageView(Context context, AttributeSet attrs) {
            super(context, attrs);
    
            setScaleType(ScaleType.MATRIX);
            mGestureDetector = new GestureDetector(context,
                    new SimpleOnGestureListener() {
                        @Override
                        public boolean onDoubleTap(MotionEvent e) {
                            if (isAutoScale)
                                return true;
    
                            float x = e.getX();
                            float y = e.getY();
                            if (getScale() < mScaleMin) {
                                ClipImageView.this.postDelayed(new AutoScaleRunnable(mScaleMin, x, y), 16);
                            } else {
                                ClipImageView.this.postDelayed(new AutoScaleRunnable(mInitScale, x, y), 16);
                            }
                            isAutoScale = true;
    
                            return true;
                        }
                    });
            mScaleGestureDetector = new ScaleGestureDetector(context, this);
            this.setOnTouchListener(this);
    
            mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
            mPaint.setColor(Color.WHITE);
    
            TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.ClipImageView);
            mWidth = ta.getInteger(R.styleable.ClipImageView_civWidth, 1);
            mHeight = ta.getInteger(R.styleable.ClipImageView_civHeight, 1);
            mClipPadding = ta.getDimensionPixelSize(R.styleable.ClipImageView_civClipPadding, 0);
            mTipText = ta.getString(R.styleable.ClipImageView_civTipText);
            mMaskColor = ta.getColor(R.styleable.ClipImageView_civMaskColor, 0xB2000000);
            final int textSize = ta.getDimensionPixelSize(R.styleable.ClipImageView_civTipTextSize, 24);
            mPaint.setTextSize(textSize);
            ta.recycle();
    
            mPaint.setDither(true);
        }

    定义裁剪框

    裁剪框的位置

    裁剪框是在控件正中间的,首先我们从属性中读取到的是宽高的比例,以及左右边距,但是在构造方法中,由于控件还没有绘制出来,无法获取到控件的宽高,所以并不能计算裁剪框的大小和位置。所以我重写了onLayout方法,在这里计算裁剪框的位置:

        @Override
        protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
            super.onLayout(changed, left, top, right, bottom);
            final int width = getWidth();
            final int height = getHeight();
            mClipBorder.left = mClipPadding;
            mClipBorder.right = width - mClipPadding;
            final int borderHeight = mClipBorder.width() * mHeight / mWidth;
            mClipBorder.top = (height - borderHeight) / 2;
            mClipBorder.bottom = mClipBorder.top + borderHeight;
        }

    绘制裁剪框

    这里我顺便把绘制提示文字的代码也一并给出,都是在同一个方法里的。很简单,重写onDraw方法即可。绘制裁剪框有两种方法,一是绘制一个满屏的遮罩层,然后从中间抠出一个长方形出来,但是我用的时候发现抠不出来,所以我采用的是下面这一种:
    这里写图片描述
    先画上下两个矩形,再画左右两个矩形,中间所围起来的没有画的部分就是我们的裁剪框。

        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            final int width = getWidth();
            final int height = getHeight();
    
            mPaint.setColor(mMaskColor);
            mPaint.setStyle(Paint.Style.FILL);
            canvas.drawRect(0, 0, width, mClipBorder.top, mPaint);
            canvas.drawRect(0, mClipBorder.bottom, width, height, mPaint);
            canvas.drawRect(0, mClipBorder.top, mClipBorder.left, mClipBorder.bottom, mPaint);
            canvas.drawRect(mClipBorder.right, mClipBorder.top, width, mClipBorder.bottom, mPaint);
    
            mPaint.setColor(Color.WHITE);
            mPaint.setStrokeWidth(1);
            mPaint.setStyle(Paint.Style.STROKE);
            canvas.drawRect(mClipBorder.left, mClipBorder.top, mClipBorder.right, mClipBorder.bottom, mPaint);
    
            if (mTipText != null) {
                final float textWidth = mPaint.measureText(mTipText);
                final float startX = (width - textWidth) / 2;
                final Paint.FontMetrics fm = mPaint.getFontMetrics();
                final float startY = mClipBorder.bottom + mClipBorder.top / 2 - (fm.descent - fm.ascent) / 2;
                mPaint.setStyle(Paint.Style.FILL);
                canvas.drawText(mTipText, startX, startY, mPaint);
            }
        }

    修改图片的初始显示

    这里我不使用全局布局的监听(通过getViewTreeObserver加入回调),而是直接重写几个设置图片的方法,在设置图片后进行初始显示的设置:

        @Override
        public void setImageDrawable(Drawable drawable) {
            super.setImageDrawable(drawable);
            postResetImageMatrix();
        }
    
        @Override
        public void setImageResource(int resId) {
            super.setImageResource(resId);
            postResetImageMatrix();
        }
    
        @Override
        public void setImageURI(Uri uri) {
            super.setImageURI(uri);
            postResetImageMatrix();
        }
    
        private void postResetImageMatrix() {
            post(new Runnable() {
                @Override
                public void run() {
                    resetImageMatrix();
                }
            });
        }

    resetImageMatrix()方法设置图片的初始缩放及平移,参考图片大小,控件本身大小,以及裁剪框的大小进行计算:

        /**
         * 垂直方向与View的边矩
         */
        public void resetImageMatrix() {
            final Drawable d = getDrawable();
            if (d == null) {
                return;
            }
    
            final int dWidth = d.getIntrinsicWidth();
            final int dHeight = d.getIntrinsicHeight();
    
            final int cWidth = mClipBorder.width();
            final int cHeight = mClipBorder.height();
    
            final int vWidth = getWidth();
            final int vHeight = getHeight();
    
            final float scale;
            final float dx;
            final float dy;
    
            if (dWidth * cHeight > cWidth * dHeight) {
                scale = cHeight / (float) dHeight;
            } else {
                scale = cWidth / (float) dWidth;
            }
    
            dx = (vWidth - dWidth * scale) * 0.5f;
            dy = (vHeight - dHeight * scale) * 0.5f;
    
            mScaleMatrix.setScale(scale, scale);
            mScaleMatrix.postTranslate((int) (dx + 0.5f), (int) (dy + 0.5f));
    
            setImageMatrix(mScaleMatrix);
    
            mInitScale = scale;
            mScaleMin = mInitScale * 2;
            mScaleMax = mInitScale * 4;
        }

    注意:这里有一个坑。把一个Bitmap设置到ImageView中,显示时要计算的是ImageView获取的Drawable对象以及这个对象的宽高,而不是Bitmap对象。Drawable对象可能由于对Bitmap的放大或缩小显示,导致它的宽或高与Bitmap的宽高不同。
    还有一点小注意:获取控件宽高是要在控件被绘制出来之后才能获取得到的,所以上面我通过post一个Runnable对象到主线程的Looper中,保证它是在界面绘制完成之后被调用。

    缩放及拖动

    缩放及拖动时都需求判断是否超出边界,如果超出,则取允许的最终值。这里的代码我没怎么动,稍后可直接参考源码,暂不赘述。

    裁剪

    这里是另外一个改造的重点了。
    首先,鸿洋大神是通过创建一个空的Bitmap,并根据它创建出一个Canvas对象,然后通过draw方法把缩放后的图片给绘制到这个Bitmap中,再调用Bitmap.createBitmap得到属于裁剪框的内容。但是我们已经重写了onDraw方法画出裁剪框,所以这里就不考虑了。
    另外,这种方法还有一个问题:它绘制的是Drawable对象。如果我们设置进去的是一个比较大的Bitmap,那么就可能被缩放了,这里裁剪的是缩放后的Bitmap,也就是它不是对原图进行裁剪的。
    这里我参考了其他裁剪图片库,通过保存了缩放平移的Matrix成员变量进行计算,获取出裁剪框在其的对应范围,并根据最终所需(我们产品要限制一个最大大小),得到最终的图片,代码如下:

        public Bitmap clip() {
            final Drawable drawable = getDrawable();
            final Bitmap originalBitmap = ((BitmapDrawable) drawable).getBitmap();
    
            final float[] matrixValues = new float[9];
            mScaleMatrix.getValues(matrixValues);
            final float scale = matrixValues[Matrix.MSCALE_X] * drawable.getIntrinsicWidth() / originalBitmap.getWidth();
            final float transX = matrixValues[Matrix.MTRANS_X];
            final float transY = matrixValues[Matrix.MTRANS_Y];
    
            final float cropX = (-transX + mClipBorder.left) / scale;
            final float cropY = (-transY + mClipBorder.top) / scale;
            final float cropWidth = mClipBorder.width() / scale;
            final float cropHeight = mClipBorder.height() / scale;
    
            Matrix outputMatrix = null;
            if (mMaxOutputWidth > 0 && cropWidth > mMaxOutputWidth) {
                final float outputScale = mMaxOutputWidth / cropWidth;
                outputMatrix = new Matrix();
                outputMatrix.setScale(outputScale, outputScale);
            }
    
            return Bitmap.createBitmap(originalBitmap,
                    (int) cropX, (int) cropY, (int) cropWidth, (int) cropHeight,
                    outputMatrix, false);
        }

    由于我们是对Bitmap进行裁剪,所以首先获取这个Bitmap

            final Drawable drawable = getDrawable();
            final Bitmap originalBitmap = ((BitmapDrawable) drawable).getBitmap();

    然后,我们的矩阵值可以通过一个包含9个元素的float数组读出:

            final float[] matrixValues = new float[9];
            mScaleMatrix.getValues(matrixValues);

    比如,读X上的缩放值,代码为matrixValues[Matrix.MSCALE_X]
    要特别注意一点,在前文也有提到,这里缩放的是Drawable对象,但是我们裁剪时用的Bitmap,如果图片太大的话是可能在Drawable上进行缩放的,所以缩放大小的计算应该为:

            final float scale = matrixValues[Matrix.MSCALE_X] * drawable.getIntrinsicWidth() / originalBitmap.getWidth();

    然后获取图片平移量:

            final float transX = matrixValues[Matrix.MTRANS_X];
            final float transY = matrixValues[Matrix.MTRANS_Y];

    计算裁剪框对应在图片上的起点及宽高:

            final float cropX = (-transX + mClipBorder.left) / scale;
            final float cropY = (-transY + mClipBorder.top) / scale;
            final float cropWidth = mClipBorder.width() / scale;
            final float cropHeight = mClipBorder.height() / scale;

    上面就是我们所要裁剪出来的最终结果。
    但是,我前面也说的,应产品需求,要限制最大输出大小。由于我们裁剪出来的图片宽高比是3:2,我这里只取宽度(你要取高度也可以)进行限制,所以又加上了如下代码,当裁剪出来的宽度超出我们最大宽度时,进行缩放。

            Matrix outputMatrix = null;
            if (mMaxOutputWidth > 0 && cropWidth > mMaxOutputWidth) {
                final float outputScale = mMaxOutputWidth / cropWidth;
                outputMatrix = new Matrix();
                outputMatrix.setScale(outputScale, outputScale);
            }

    最终根据上面计算出来的值,创建裁剪出来的Bitmap:

    Bitmap.createBitmap(originalBitmap,
                    (int) cropX, (int) cropY, (int) cropWidth, (int) cropHeight,
                    outputMatrix, false);

    这样,图片裁剪控件就算全部完成。

    实现效果

    这里写图片描述

    后述

    1. 全部代码见:https://github.com/msdx/clip-image,有demo。
    2. 我在控件中还增加了一个接口getClipMatrixValues,获取裁剪时图片的矩阵值,它可用于做大图的裁剪。
    3. 有关大图的裁剪,我后续会再写一篇。
    4. 大图裁剪的代码,也在上面的demo里。
    5. 使用时可以设置裁剪框的宽高比来决定是正方形的裁剪框还是有其他比例要求的裁剪框

    本文原创,转载请注明CSDN博客出处:
    http://blog.csdn.net/maosidiaoxian/article/details/50828664

    参考资料:

    展开全文
  • 摘要: 本设计是实现Android底层驱动开发并裁剪定制Android操作系统。其大概流程是在linux系统上借助于gcc编译器编写调试驱动代码,在Android源码中编写APP应用程序、中间件的共享库代码、Android.mk文件、利用javah...
  • 第四章、Android编译系统与定制Android平台系统 4.1Android编译系统Android的源码由几十万个文件构成,这些文件之间有的相互依赖,有的又相互独立,它们按功能或类型又被放到不同目录下,对于这个大的一个工程,...
  • 前言:相信你看了《Android开发之Paint的高级使用》博客,对Paint有一定的认识,今天我带给大家一篇一个关于paint的小练习,定制我们知己的ProgressBar,这里我们不讨论google给我们提供的ProgressBar,也不讨论基于...
  • 何为系统服务做Android APP开发的过程中,会经常需要调用类似于下面这样的函数:WifiManager mWifiManager = (WifiManager)getSystemService(Context.WIFI_MANAGER_SERVICE);这个WifiManagerService就是运行在...
  • 移动端app开发,原生开发与混合开发的区别

    万次阅读 多人点赞 2019-09-26 18:47:01
    目前市场上主流的APP分为三种:原生APP、Web APP(即HTML5)和混合APP三种,相对应的定制开发就是原生开发、H5开发和混合开发。那么这三种开发模式究竟有何不同呢?下面我们就分别从这三者各自的优劣势来区分比较吧...
  • 主要为大家详细介绍了Android快速开发定制BaseTemplate的相关资料,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
  • Android使用定制framework开发应用

    千次阅读 2013-11-04 08:01:45
    Android系统作为目前开源移动操作系统的代表已经更新到了4.0.3...但是如何开发Android系统framework层那,我们知道使用Eclipse+ADT就可以进行Android 应用的开发。使用手机或模拟器进行相应效果的调试。 这是标准的应
  • Android开发--toolbar定制

    千次阅读 2016-03-02 15:35:39
    在以前的开发过程中,每次标题栏都是自己布局,每次都被美工水说没有ios做的包准,因此我发烧要学学官方的东西,那就是高大上的toolbar了,虽然出来了这么久都是一直没用过,最近看到很多demo中都用到了,装逼的我也...
  • 何为定制Android系统?就是在特定的硬件上,移植上Android操作系统,并修改原生Android系统以提供给定制的APP操作定制硬件的方法。所以,定制Android的主要工作有以下三部分: 适配硬件; 制作接口; 定制APP;
  • Android基础——快速开发定制BaseTemplate

    千次阅读 热门讨论 2017-02-27 13:39:14
    前言 初学者肯定会遇到一个日常任务,那么就是findViewById,setOnClickListener(暂且把它们称为日常任务),而且很多人会把他们混在一起,导致项目结构混乱,最主要的是写多了会烦,不觉得吗?...

空空如也

1 2 3 4 5 ... 20
收藏数 2,515
精华内容 1,006
关键字:

定制android开发