草庐IT

android - 在边界内翻译/缩放位图?

coder 2023-12-01 原文

当我研究图像上的触摸平移/缩放方法时,我通常会找到有效、简单的代码——但没有一个能完全满足我的要求。图像不需要在实际图像(位图)的边缘与其 View 之间显示空白。如果位图是 200x100 而 View 是 50x50,用户应该只能缩小到 100x50,允许他们水平滑动图像,但不能垂直滑动。

我的代码在移动(平移)图像时做得很好——直到图像被缩放。然后有些东西被扔掉了;我可以将位图移动得足够远以查看其周围的间隙。这可能是与按当前比例因子分解像素测量值相关的简单明了的东西,但我找不到它。我怀疑这与下面 onDraw() 中 maxX 和 maxY 的计算有关。有什么想法吗?

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.widget.ImageView;

/**
 * Most code from
 * http://android-developers.blogspot.com/2010/06/making-sense-of-multitouch.html
 * 
 * @author Chad Schultz
 * 
 */
public class PanZoomImageView extends ImageView {

    public static final String TAG = PanZoomImageView.class.getName();

    private static final int INVALID_POINTER_ID = -1;

    // The ‘active pointer’ is the one currently moving our object.
    private int mActivePointerId = INVALID_POINTER_ID;

    private Bitmap bitmap;

    private ScaleGestureDetector mScaleDetector;
    private float mScaleFactor = 1.f;
    private float minScaleFactor;

    private float mPosX;
    private float mPosY;

    private float mLastTouchX, mLastTouchY;

    private boolean firstDraw = true;

    private boolean panEnabled = true;
    private boolean zoomEnabled = true;

    public PanZoomImageView(Context context) {
        super(context);
        setup();
    }

    public PanZoomImageView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        setup();
    }

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

    private void setup() {
        mScaleDetector = new ScaleGestureDetector(getContext(), new ScaleListener());
    }

    @Override
    public void setImageBitmap(Bitmap bmp) {
        super.setImageBitmap(bmp);
        bitmap = bmp;
        firstDraw = true;
    }

    @Override
    public void setImageDrawable(Drawable drawable) {
        super.setImageDrawable(drawable);
        bitmap = ((BitmapDrawable) drawable).getBitmap();
        firstDraw = true;
    }

    public void onDraw(Canvas canvas) {
        Log.v(TAG, "onDraw()");
        if (bitmap == null) {
            Log.w(TAG, "nothing to draw - bitmap is null");
            super.onDraw(canvas);
            return;
        }

        if (firstDraw 
                && (bitmap.getHeight() > 0) 
                && (bitmap.getWidth() > 0) 
                && (canvas.getHeight() > 0) 
                && (canvas.getWidth() > 0)) {
            //Don't let the user zoom out so much that the image is smaller
            //than its containing frame
            float minXScaleFactor = (float) canvas.getWidth() / (float) bitmap.getWidth();
            float minYScaleFactor = (float) canvas.getHeight() / (float) bitmap.getHeight();
            minScaleFactor = Math.max(minXScaleFactor, minYScaleFactor);
            Log.d(TAG, "minScaleFactor: " + minScaleFactor);
            firstDraw = false;
        }
        mScaleFactor = Math.max(mScaleFactor, minScaleFactor);
        Log.d(TAG, "mScaleFactor: " + mScaleFactor);

        //Save the canvas without translating (panning) or scaling (zooming)
        //After each change, restore to this state, instead of compounding
        //changes upon changes
        canvas.save();

        int maxX, minX, maxY, minY;
        //How far can we move the image horizontally without having a gap between image and frame?
        maxX = (int) (mScaleFactor * (bitmap.getWidth() / 2) - (canvas.getWidth() / 2));
        minX = -1 * maxX;
        //How far can we move the image vertically without having a gap between image and frame?
        maxY = (int) (mScaleFactor * (bitmap.getHeight() / 2) - (canvas.getWidth() / 2));
        minY = -1 * maxY;
        //Do not go beyond the boundaries of the image
        if (mPosX > maxX) {
            mPosX = maxX;
        }
        if (mPosX < minX) {
            mPosX = minX;
        }
        if (mPosY > maxY) {
            mPosY = maxY;
        }
        if (mPosY < minY) {
            mPosY = minY;
        }

        Log.d(TAG, "canvas width: " + canvas.getWidth() + " canvas height: "
                + canvas.getHeight());
        Log.d(TAG, "bitmap width: " + bitmap.getWidth() + " height: " + bitmap.getHeight());
        Log.d(TAG, "translating mPosX: " + mPosX + " mPosY: " + mPosY);

        if (zoomEnabled) {
            Log.d(TAG, "zooming to scale factor of " + mScaleFactor);
            canvas.scale(mScaleFactor, mScaleFactor);
        } else {
            Log.d(TAG, "zooming disabled");
        }

        if (panEnabled) {
            Log.d(TAG, "panning to " + mPosX + "," + mPosY); 
            canvas.translate(mPosX, mPosY);
        } else {
            Log.d(TAG, "panning disabled");
        }

        super.onDraw(canvas);
        canvas.restore(); //clear translation/scaling
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        // Let the ScaleGestureDetector inspect all events.
        mScaleDetector.onTouchEvent(ev);

        final int action = ev.getAction();
        switch (action & MotionEvent.ACTION_MASK) {
            case MotionEvent.ACTION_DOWN: {
                final float x = ev.getX();
                final float y = ev.getY();

                mLastTouchX = x;
                mLastTouchY = y;
                mActivePointerId = ev.getPointerId(0);
                break;
            }

            case MotionEvent.ACTION_MOVE: {
                final int pointerIndex = ev.findPointerIndex(mActivePointerId);
                final float x = ev.getX(pointerIndex);
                final float y = ev.getY(pointerIndex);

                // Only move if the ScaleGestureDetector isn't processing a gesture.
                if (!mScaleDetector.isInProgress()) {
                    float dx = x - mLastTouchX;
                    float dy = y - mLastTouchY;

                    //Adjust for zoom factor. Otherwise, the user's finger moving 10 pixels
                    //at 200% zoom causes the image to slide 20 pixels instead of perfectly
                    //following the user's touch
                    dx /= mScaleFactor;
                    dy /= mScaleFactor;

                    mPosX += dx;
                    mPosY += dy;

                    invalidate();
                }

                mLastTouchX = x;
                mLastTouchY = y;

                break;
            }

            case MotionEvent.ACTION_UP: {
                mActivePointerId = INVALID_POINTER_ID;
                break;
            }

            case MotionEvent.ACTION_CANCEL: {
                mActivePointerId = INVALID_POINTER_ID;
                break;
            }

            case MotionEvent.ACTION_POINTER_UP: {
                final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
                final int pointerId = ev.getPointerId(pointerIndex);
                if (pointerId == mActivePointerId) {
                    // This was our active pointer going up. Choose a new
                    // active pointer and adjust accordingly.
                    final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
                    mLastTouchX = ev.getX(newPointerIndex);
                    mLastTouchY = ev.getY(newPointerIndex);
                    mActivePointerId = ev.getPointerId(newPointerIndex);
                }
                break;
            }
        }

        return true;
    }

    private class ScaleListener extends
            ScaleGestureDetector.SimpleOnScaleGestureListener {
        @Override
        public boolean onScale(ScaleGestureDetector detector) {
            mScaleFactor *= detector.getScaleFactor();
            // Don't let the object get too small or too large.
            mScaleFactor = Math.max(0.1f, Math.min(mScaleFactor, 5.0f));
            Log.d(TAG, "detector scale factor: " + detector.getScaleFactor() + " mscalefactor: " + mScaleFactor);

            invalidate();
            return true;
        }
    }

    //Currently zoomEnabled/panEnabled can only be set programmatically, not in XML

    public boolean isPanEnabled() {
        return panEnabled;
    }

    public void setPanEnabled(boolean panEnabled) {
        this.panEnabled = panEnabled;
    }

    public boolean isZoomEnabled() {
        return zoomEnabled;
    }

    public void setZoomEnabled(boolean zoomEnabled) {
        this.zoomEnabled = zoomEnabled;
    }

}

最佳答案

经过大量痛苦的实验,我最终得出了自己的结论——在此过程中学习了一些关于 Android 中位图处理方式的有趣知识。这段代码远非完美,但它符合我的目的——希望它也能帮助其他人。

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.View;

/**
 * @author Chad Schultz
 * @version 1
 */
public class PanZoomView extends View {

    public static final String TAG = PanZoomView.class.getName();

    private static final int INVALID_POINTER_ID = -1;

    // The ‘active pointer’ is the one currently moving our object.
    private int mActivePointerId = INVALID_POINTER_ID;

    private Bitmap bitmap;
    private float viewHeight;
    private float viewWidth;
    float canvasWidth, canvasHeight;

    private ScaleGestureDetector mScaleDetector;
    private float mScaleFactor = 1.f;
    private float minScaleFactor;

    private float mPosX;
    private float mPosY;

    private float mLastTouchX, mLastTouchY;

    private boolean firstDraw = true;

    private boolean panEnabled = true;
    private boolean zoomEnabled = true;

    public PanZoomView(Context context) {
        super(context);
        setup();
    }

    public PanZoomView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        setup();
    }

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

    private void setup() {
        mScaleDetector = new ScaleGestureDetector(getContext(), new ScaleListener());
    }

    public void setBitmap(Bitmap bmp) {
        setImageBitmap(bmp);
    }

    public void setImageBitmap(Bitmap bmp) {
        bitmap = bmp;
        resetZoom();
        resetPan();
        firstDraw = true;
        invalidate();
    }

    public Bitmap getImageBitmap() {
        return bitmap;
    }

    public Bitmap getBitmap() {
        return getImageBitmap();
    }

    public void resetZoom() {
        mScaleFactor = 1.0f;
    }

    public void resetPan() {
        mPosX = 0f;
        mPosY = 0f;
    }

    public void setImageDrawable(Drawable drawable) {
        setImageBitmap(((BitmapDrawable) drawable).getBitmap());
    }

    public BitmapDrawable getImageDrawable() {
        BitmapDrawable bd = new BitmapDrawable(getContext().getResources(), bitmap);
        return bd;
    }

    public BitmapDrawable getDrawable() {
        return getImageDrawable();
    }

    public void onDraw(Canvas canvas) {
//      Log.v(TAG, "onDraw()");

        if (bitmap == null) {
            Log.w(TAG, "nothing to draw - bitmap is null");
            super.onDraw(canvas);
            return;
        }

        if (firstDraw 
                && (bitmap.getHeight() > 0) 
                && (bitmap.getWidth() > 0)) {
            //Don't let the user zoom out so much that the image is smaller
            //than its containing frame
            float minXScaleFactor = (float) viewWidth / (float) bitmap.getWidth();
            float minYScaleFactor = (float) viewHeight / (float) bitmap.getHeight();
            minScaleFactor = Math.max(minXScaleFactor, minYScaleFactor);
            Log.d(TAG, "minScaleFactor: " + minScaleFactor);
            mScaleFactor = minScaleFactor; //start out "zoomed out" all the way

            mPosX = mPosY = 0;
            firstDraw = false;

        }
        mScaleFactor = Math.max(mScaleFactor, minScaleFactor);

        canvasHeight = canvas.getHeight();
        canvasWidth = canvas.getWidth();
//      Log.d(TAG, "canvas density: " + canvas.getDensity() + " bitmap density: " + bitmap.getDensity());

//      Log.d(TAG, "mScaleFactor: " + mScaleFactor);

        //Save the canvas without translating (panning) or scaling (zooming)
        //After each change, restore to this state, instead of compounding
        //changes upon changes
        canvas.save();
        int maxX, minX, maxY, minY;
        //Regardless of the screen density (HDPI, MDPI) or the scale factor, 
        //The image always consists of bitmap width divided by 2 pixels. If an image
        //is 200 pixels wide and you scroll right 100 pixels, you just scrolled the image
        //off the screen to the left.
        minX = (int) (((viewWidth / mScaleFactor) - bitmap.getWidth()) / 2);
        maxX = 0;
        //How far can we move the image vertically without having a gap between image and frame?
        minY = (int) (((viewHeight / mScaleFactor) - bitmap.getHeight()) / 2);
        maxY = 0;
        Log.d(TAG, "minX: " + minX + " maxX: " + maxX + " minY: " + minY + " maxY: " + maxY);
        //Do not go beyond the boundaries of the image
        if (mPosX > maxX) {
            mPosX = maxX;
        }
        if (mPosX < minX) {
            mPosX = minX;
        }
        if (mPosY > maxY) {
            mPosY = maxY;
        }
        if (mPosY < minY) {
            mPosY = minY;
        }

//      Log.d(TAG, "view width: " + viewWidth + " view height: "
//              + viewHeight);
//      Log.d(TAG, "bitmap width: " + bitmap.getWidth() + " height: " + bitmap.getHeight());
//      Log.d(TAG, "translating mPosX: " + mPosX + " mPosY: " + mPosY);

//      Log.d(TAG, "zooming to scale factor of " + mScaleFactor);
        canvas.scale(mScaleFactor, mScaleFactor);

//      Log.d(TAG, "panning to " + mPosX + "," + mPosY); 
        canvas.translate(mPosX, mPosY);

        super.onDraw(canvas);
        canvas.drawBitmap(bitmap, mPosX, mPosY, null);
        canvas.restore(); //clear translation/scaling
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        // Let the ScaleGestureDetector inspect all events.
        if (zoomEnabled) {
            mScaleDetector.onTouchEvent(ev);
        }

        if (panEnabled) {
            final int action = ev.getAction();
            switch (action & MotionEvent.ACTION_MASK) {
                case MotionEvent.ACTION_DOWN: {
                    final float x = ev.getX();
                    final float y = ev.getY();

                    mLastTouchX = x;
                    mLastTouchY = y;
                    mActivePointerId = ev.getPointerId(0);
                    break;
                }

                case MotionEvent.ACTION_MOVE: {
                    final int pointerIndex = ev.findPointerIndex(mActivePointerId);
                    final float x = ev.getX(pointerIndex);
                    final float y = ev.getY(pointerIndex);

                    // Only move if the ScaleGestureDetector isn't processing a gesture.
                    if (!mScaleDetector.isInProgress()) {
                        float dx = x - mLastTouchX;
                        float dy = y - mLastTouchY;

                        //Adjust for zoom factor. Otherwise, the user's finger moving 10 pixels
                        //at 200% zoom causes the image to slide 20 pixels instead of perfectly
                        //following the user's touch
                        dx /= (mScaleFactor * 2);
                        dy /= (mScaleFactor * 2);

                        mPosX += dx;
                        mPosY += dy;

                        Log.v(TAG, "moving by " + dx + "," + dy + " mScaleFactor: " + mScaleFactor);

                        invalidate();
                    }

                    mLastTouchX = x;
                    mLastTouchY = y;

                    break;
                }

                case MotionEvent.ACTION_UP: {
                    mActivePointerId = INVALID_POINTER_ID;
                    break;
                }

                case MotionEvent.ACTION_CANCEL: {
                    mActivePointerId = INVALID_POINTER_ID;
                    break;
                }

                case MotionEvent.ACTION_POINTER_UP: {
                    final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
                    final int pointerId = ev.getPointerId(pointerIndex);
                    if (pointerId == mActivePointerId) {
                        // This was our active pointer going up. Choose a new
                        // active pointer and adjust accordingly.
                        final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
                        mLastTouchX = ev.getX(newPointerIndex);
                        mLastTouchY = ev.getY(newPointerIndex);
                        mActivePointerId = ev.getPointerId(newPointerIndex);
                    }
                    break;
                }
            }
        }
        return true;
    }

    private class ScaleListener extends
            ScaleGestureDetector.SimpleOnScaleGestureListener {
        @Override
        public boolean onScale(ScaleGestureDetector detector) {
            mScaleFactor *= detector.getScaleFactor();
            // Don't let the object get too small or too large.
            mScaleFactor = Math.max(0.1f, Math.min(mScaleFactor, 5.0f));
//          Log.d(TAG, "detector scale factor: " + detector.getScaleFactor() + " mscalefactor: " + mScaleFactor);

            invalidate();
            return true;
        }
    }

    //Currently zoomEnabled/panEnabled can only be set programmatically, not in XML

    public boolean isPanEnabled() {
        return panEnabled;
    }

    public void setPanEnabled(boolean panEnabled) {
        this.panEnabled = panEnabled;
    }

    public boolean isZoomEnabled() {
        return zoomEnabled;
    }

    public void setZoomEnabled(boolean zoomEnabled) {
        this.zoomEnabled = zoomEnabled;
    }

    /**
     * Calls getCroppedBitmap(int outputWidth, int outputHeight) without
     * scaling the resulting bitmap to any specific size.
     * @return
     */
    public Bitmap getCroppedBitmap() {
        return getCroppedBitmap(0, 0);
    }

    /**
     * Takes the section of the bitmap visible in its View object
     * and exports that to a Bitmap object, taking into account both
     * the translation (panning) and zoom (scaling).
     * WARNING: run this in a separate thread, not on the UI thread!
     * If you specify that a 200x200 image should have an outputWidth
     * of 400 and an outputHeight of 50, the image will be squished
     * and stretched to those dimensions.
     * @param outputWidth desired width of output Bitmap in pixels
     * @param outputHeight desired height of output Bitmap in pixels
     * @return the visible portion of the image in the PanZoomImageView
     */
    public Bitmap getCroppedBitmap(int outputWidth, int outputHeight) {
        int origX = -1 * (int) mPosX * 2;
        int origY = -1 * (int) mPosY * 2;
        int width = (int) (viewWidth / mScaleFactor);
        int height = (int) (viewHeight / mScaleFactor);
        Log.e(TAG, "origX: " + origX + " origY: " + origY + " width: " + width + " height: " + height + " outputWidth: " + outputWidth + " outputHeight: " + outputHeight + "getLayoutParams().width: " + getLayoutParams().width + " getLayoutParams().height: " + getLayoutParams().height);
        Bitmap b = Bitmap.createBitmap(bitmap, origX, origY, width, height);

        if (outputWidth > 0 && outputWidth > 0) {
            //Use the exact dimensions given--chance this won't match the aspect ratio
            b = Bitmap.createScaledBitmap(b, outputWidth, outputHeight, true);
        }

        return b;
    }

      @Override
        protected void onSizeChanged(int w, int h, int oldw, int oldh) {
            super.onSizeChanged(w, h, oldw, oldh);
            viewHeight = h;
            viewWidth = w;
        }
}

关于android - 在边界内翻译/缩放位图?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/13961223/

有关android - 在边界内翻译/缩放位图?的更多相关文章

  1. ruby - i18n Assets 管理/翻译 UI - 2

    我正在使用i18n从头开始​​构建一个多语言网络应用程序,虽然我自己可以处理一大堆yml文件,但我说的语言(非常)有限,最终我想寻求外部帮助帮助。我想知道这里是否有人在使用UI插件/gem(与django上的django-rosetta不同)来处理多个翻译器,其中一些翻译器不愿意或无法处理存储库中的100多个文件,处理语言数据。谢谢&问候,安德拉斯(如果您已经在ruby​​onrails-talk上遇到了这个问题,我们深表歉意) 最佳答案 有一个rails3branchofthetolkgem在github上。您可以通过在Gemfi

  2. 安卓apk修改(Android反编译apk) - 2

    最近因为项目需要,需要将Android手机系统自带的某个系统软件反编译并更改里面某个资源,并重新打包,签名生成新的自定义的apk,下面我来介绍一下我的实现过程。APK修改,分为以下几步:反编译解包,修改,重打包,修改签名等步骤。安卓apk修改准备工作1.系统配置好JavaJDK环境变量2.需要root权限的手机(针对系统自带apk,其他软件免root)3.Auto-Sign签名工具4.apktool工具安卓apk修改开始反编译本文拿Android系统里面的Settings.apk做demo,具体如何将apk获取出来在此就不过多介绍了,直接进入主题:按键win+R输入cmd,打开命令窗口,并将路

  3. ruby - Google-api-ruby-client 翻译 API 示例 - 2

    很高兴看到google代码:google-api-ruby-client项目,因为这对我来说意味着Ruby人员可以使用GoogleAPI-s来完善代码。虽然我现在很困惑,因为给出的唯一示例使用Buzz,并且根据我的实验,Google翻译(v2)api的行为必须与google-api-ruby-client中的Buzz完全不同。.我对“Explorer”演示示例很感兴趣——但据我所知,它并不是一个探索器。它所做的只是调用一个Buzz服务,然后浏览它已经知道的关于Buzz服务的事情。对我来说,Explorer应该让您“发现”所公开的服务和方法/功能,而不一定已经知道它们。我很想听听使用这个

  4. ruby-on-rails - 如果特定语言环境中缺少翻译,如何配置 i18n 以使用 en 语言环境? - 2

    如果特定语言环境中缺少翻译,如何配置i18n以使用en语言环境翻译?当前已插入翻译缺失消息。我正在使用RoR3.1。 最佳答案 找到相似的question这里是答案:#application.rb#railswillfallbacktoconfig.i18n.default_localetranslationconfig.i18n.fallbacks=true#railswillfallbacktoen,nomatterwhatissetasconfig.i18n.default_localeconfig.i18n.fallback

  5. ruby-on-rails - 如何使用 globalize 和 rails 4 以一种形式显示所有翻译字段 - 2

    在使用rails4和https://github.com/globalize/globalize的情况下,我应该如何为我的模型编写表单?用于翻译。我想以一种形式显示所有翻译,如下例所示。我在这里找到了解决方案https://github.com/rilla/batch_translations但我不知道如何实现它。这个“批量翻译”是一个gem还是什么?以及如何安装它。EditingpostEnglish(defaultlocale)SpanishtranslationFrenchtranslation 最佳答案 批处理翻译gem很旧

  6. python - 用于从 Python 到 Ruby 查找集合的所有分区的翻译函数 - 2

    我有以下python函数来递归查找集合的所有分区:defpartitions(set_):ifnotset_:yield[]returnforiinxrange(2**len(set_)/2):parts=[set(),set()]foriteminset_:parts[i&1].add(item)i>>=1forbinpartitions(parts[1]):yield[parts[0]]+bforpinpartitions(["a","b","c","d"]):print(p)有人可以帮我把它翻译成ruby​​吗?这是我目前所拥有的:defpartitions(set)ifnots

  7. ruby - I18n.t 翻译缺少默认值 Nil - 2

    所以我知道如果我在读取yaml文件时遇到“翻译缺失:”如何返回默认值。some=I18n.t("something.something_else",default:"value")但是如果我希望默认值为nil,我该如何以Ruby的方式做到这一点呢?我知道我可以正则表达式并匹配变量some中的“translationmissing:”,如果它匹配,我会将它分配给nil。但我想做的是拥有some=I18n.t("something.something_else",default:nil)但它只是返回了我缺少的翻译。有谁知道好的方法吗? 最佳答案

  8. ruby-on-rails - 翻译 Rails 模型关联 - 不工作 - 2

    有人知道如何在Rails中翻译模型关联吗?例如:我有一个Person模型,它可以有很多Phone。但是,一个人需要至少有一部电话。我无法翻译该验证。我能做的最好的是:validates_presence_of:phones,:message=>"Atleastonephoneisrequired."在我的YAML上,我替换了这一行以省略%{attribute}:format:!'%{message}'这样只显示我的消息,我避免显示未翻译的字段名称。这让我很头疼,因为有些gems根本不允许我传递:message=>"somethingdescribingtheerror",所以我想配置所

  9. ruby-on-rails - 将 gem 中的 I18n 翻译动态加载到 Rails 引擎中 - 2

    我创建了一个gem(TranslationsGem),我在多个项目(一个引擎和一个Rails应用程序)中使用它。这个gem设置了几个哈希值,这些哈希值被加载到I18n后端。#store_dynamic_translations方法设置了几个哈希,这些哈希被加载到I18n后端。它基本上是这样工作的:I18n.backend.store_translations(:en,{test:{property:'value'}})我的测试确认方法和翻译加载工作正常。但是我无法让它在主机引擎和Rails应用程序中工作。在我的测试环境中,我必须在我的test_helper中执行该方法,以确保正确加载翻

  10. Android Studio开发之使用内容组件Content获取通讯信息讲解及实战(附源码 包括添加手机联系人和发短信) - 2

    运行有问题或需要源码请点赞关注收藏后评论区留言一、利用ContentResolver读写联系人在实际开发中,普通App很少会开放数据接口给其他应用访问。内容组件能够派上用场的情况往往是App想要访问系统应用的通讯数据,比如查看联系人,短信,通话记录等等,以及对这些通讯数据及逆行增删改查。首先要给AndroidMaifest.xml中添加响应的权限配置 下面是往手机通讯录添加联系人信息的例子效果如下分成三个步骤先查出联系人的基本信息,然后查询联系人号码,再查询联系人邮箱代码 ContactAddActivity类packagecom.example.chapter07;importandroid

随机推荐