适用于所有设备的Phonegap通用签名捕获

时间:2014-02-08 19:15:00

标签: android ios cordova windows-phone cross-platform

我想完成以下事情:

使用触摸屏设备获取签名,签名看起来不错,屏幕在用户签名时响应,并且可以跨平台工作。

了Emil

1 个答案:

答案 0 :(得分:0)

经过2周的挖掘,编码和测试以及失败,阅读和重试后,我设法将一些东西放在一起:

可能的解决方案:

  1. 每个平台的原生应用代码,但是您有多个应用程序而不是一个
  2. 使用canvas,差不多好,只是在Android上有2个问题:
    • Android浏览器往往会跳过相当多的触摸事件,因此在画布中绘制签名时看起来很差
    • 在运行2.3及更低版本的设备上,由于不支持功能,因此将canvas转换为base64会遇到困难
  3. 我的解决方案是: - 使用PhoneGap,并在所有其他设备上使用Android和canvas上的本机签名捕获活动。事实证明比说完成更容易。经过大量的努力,我已经设法将它组合在一起,这就是我想与您分享的内容,这只是主干,因此您可以在自己的项目中实现它。

    首先,您需要安装phonegap,而不是创建新项目。您需要拥有以下2个插件:InAppBrowser和Device,请参阅文档如何获取此插件。

    创建项目后,请为index.html使用以下代码(注意jq.js是jquery的本地副本,因此应用程序可以脱机运行):

    <html>
    <head>
            <title>Simple Signature Capture</title>
    </head>
    <body>
        <div>
        <input type=button value='GET SIGNATURE' id=sign>
        </div>
    </body>
    </html> 
    
        <script type="text/javascript" src="phonegap.js"></script>
        <script type="text/javascript" src="jq.js"></script>
        <script type="text/javascript">
         var signatrue="";
            $("#sign").click(function(){
    
            if (device.platform!=='Android') {
            // Use canvas method to get signature if device is different than Android
            var ref = window.open('sign.html', '_blank', 'location=no');
            ref.addEventListener('loaderror', function(event) { ref.close(); signatrue=event.url; print_picture();});
                        //As you see force the error listener and we assign the URL value to our variable
            }
            // Use native android activity to get signature
            else {
                        // call to native function which starts the new intent
            window.HelloWorld.customFunctionCalled();
                        // function which will be called by the intent on result
            function got_signature(){
                        // call to native function which will give us a variable which contains the signature in base4 format
            signature=window.HelloWorld.send_picture();
            print_picture();
            }
            }
            });
    function print_picture()
    {
    alert(signatrue);
    }
    </script>
    

    现在我们需要一个名为sign.html的文件,如果我们不在Android上,它将获取我们的签名,这里是代码(注意jq.js是jQuery的本地副本):

        <style>
        .container, html, body{
        height:100%;
        width:100%;
        overflow:none;
        padding:0;
        margin:0;
        }
        input[type=button] {
        width:50%;
        height:auto;
        text-align:center;
        font-size:18pt;
        float:left;
        }
        .sign_place
        {
        width:100%;
        clear:both;
        height:auto;
        }
        </style>
        <script type="text/javascript" src="jq.js"></script>
        <html>
        <div class=container>
        <input type=button id=clear value='Clear'><input type=button id=save value='Save'>
        <div class="sign_place"></div>
        </div>
        </html>
        <script>
        //reset the canvas
        $("#clear").click(function(){canvas.width = canvas.width;});
        // save the canvas and pass back the value to the parent - very dirty and cheap to force and error page so we can catch the string in the main window. 
        //data:image/png;base64, beginning must be removed otherwise browser will display the freshly captured image
        $("#save").click(function(){var pngUrl = canvas.toDataURL().replace("data:image/png;base64,",""); window.location.href=pngUrl; });
        width=$("html").width()-10;
        height=$("html").height()-$("#clear").height()-10;
        $(".sign_place").html("<canvas id='signpad' width="+width+" height="+height+" style='padding:0pxmargin:0px;border:0px solid black;'>Sorry, your browser is not supported.</canvas>");
        var canvas = document.getElementById('signpad');
        var context = canvas.getContext('2d');
            // create a drawer which tracks touch movements
            var drawer = {
                isDrawing: false,
                touchstart: function (coors) {
                context.beginPath();
                context.moveTo(coors.x, coors.y);
                this.isDrawing = true;
                },
                touchmove: function (coors) {
                    if (this.isDrawing) {
                        context.lineTo(coors.x, coors.y);
                        context.lineJoin = 'round';
                        context.lineWidth=5;
                        // adjust the lineWidth to look good. 
                        context.stroke();
    
                    }
                },
                touchend: function (coors) {
                    if (this.isDrawing) {
                        this.touchmove(coors);
                        this.isDrawing = false;
                    }
                }
            };
            // create a function to pass touch events and coordinates to drawer
            function draw(event) { 
                var type = null;
    
                var coors;
                if(event.type === "touchend") {
                    coors = {
                        x: event.changedTouches[0].pageX,
                        y: event.changedTouches[0].pageY
                    };
                }
                else {
                    // get the touch coordinates
                    coors = {
                        x: event.touches[0].pageX,
                        y: event.touches[0].pageY
                    };
                }
                type = type || event.type
                // pass the coordinates to the appropriate handler
                drawer[type](coors);
            }
            // Listen for touch events and draw
                document.addEventListener('touchstart', draw, false);
                document.addEventListener('touchmove', draw, false);
                document.addEventListener('touchend', draw, false);        
            // prevent elastic scrolling
            document.body.addEventListener('touchmove', function (event) {
                event.preventDefault();
            }, false); // end body.onTouchMove
            // Redo the canvas on orientation change
             window.addEventListener('orientationchange', function(){
             // new to reduce max width and height otherwise canvas will be larger than the screen :(
             width=$("html").width()-10;
             height=$("html").height()-$("#clear").height()-10;
             // do not use CSS to adjust it it will stretch the signature and it will look unrealistic.
             $("#signpad").attr('width',width);
             $("#signpad").attr('height',height);
    
             });
    
        </script>
    

    一切都很好,几乎准备好了,在你做之前确保你将当前jQuery的副本保存到jq.js名下的www文件夹中 使用phonegap为所需平台构建应用程序(iOS,Android,Windows Phone,BB)。

    构建之后,转到Android构建并将主要活动java修改为以下内容:

    public class HelloWorld extends CordovaActivity 
    {
    //We will need this global string to pass the signature from an activity to our phonegap app
    String signature="";
    
    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        super.init();
        // Enable the Java <-> Javascript interaction
        appView.addJavascriptInterface(this, "HelloWorld");
        // Set by <content src="index.html" /> in config.xml
        super.loadUrl(Config.getStartUrl());
        //super.loadUrl("file:///android_asset/www/sign.html");
    }
    
    //@JavascriptInterface annotation must be used for compatibility with Android 4.2 and above
    @JavascriptInterface
    public void customFunctionCalled() {
     Intent intent = new Intent(HelloWorld.this, SignatureCapture.class);
     startActivityForResult(intent,1);
    
      }
      @JavascriptInterface
       public String send_picture() {
            return signature;
      //This function only sends back the signature which is already is Base64 to the Javascript.
         }
      @JavascriptInterface
       protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    
          if (requestCode == 1) {
    
             if(resultCode == RESULT_OK){    
              signature = data.getStringExtra("RESULT_STRING");
              // Dirty trick to send Javascript command to PhoneGap
              // The trick will call the function got_signature() in Javascript
              // Which than will call the Java function get_picture
              // PhoneGap will NOT work if we try to send the image here with
              // something like super.sendJavascript("got_signature(var signature="+signature+");");
              // so do not even try to do that, it took me couple of hours
              // till I realized that there is a bug somewhere in phonegap
                super.sendJavascript("got_signature();");
           }
             if (resultCode == RESULT_CANCELED) {    
                 //Write your code if there's no result
             }
          }
        }
    }
    

    创建一个名为SignatureCapture的新类,确保您也在mainfest文件中定义它:

    public class SignatureCapture extends Activity {
     LinearLayout mContent;
        signature mSignature;
        Button mClear, mGetSign, mCancel;
    
        public int count = 1;
        public String current = null;
        private Bitmap mBitmap;
        View mView;
    
    
        private String uniqueId;
        String ba1="";
        //private EditText yourName;
    
        @Override
        public void onCreate(Bundle savedInstanceState) 
        {
            super.onCreate(savedInstanceState);
            this.requestWindowFeature(Window.FEATURE_NO_TITLE);
            setContentView(R.layout.signature);
    
    
            //prepareDirectory();
            uniqueId = getTodaysDate() + "_" + getCurrentTime() + "_" + Math.random();
            current = uniqueId + ".png";
            //mypath= new File(directory,current);
    
    
            mContent = (LinearLayout) findViewById(R.id.linearLayout);
            mSignature = new signature(this, null);
            mSignature.setBackgroundColor(Color.WHITE);
            mContent.addView(mSignature, LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT);
            mClear = (Button)findViewById(R.id.clear);
            mGetSign = (Button)findViewById(R.id.getsign);
            mGetSign.setEnabled(false);
    
            mView = mContent;
    
    
    
            mClear.setOnClickListener(new OnClickListener() 
            {        
                public void onClick(View v) 
                {
                    Log.v("log_tag", "Panel Cleared");
                    mSignature.clear();
                    mGetSign.setEnabled(false);
                }
            });
    
            mGetSign.setOnClickListener(new OnClickListener() 
            {        
                public void onClick(View v) 
                {
                    Log.v("log_tag", "Panel Saved");
                    boolean error = captureSignature();
                    if(!error){
                        mView.setDrawingCacheEnabled(true);
                        mSignature.save(mView);
                        Bundle b = new Bundle();
                        b.putString("status", "done");
                        Intent intent = new Intent();
                        intent.putExtras(b);
                        intent.putExtra("RESULT_STRING",ba1);
                        setResult(RESULT_OK,intent);   
                        finish();
                    }
                }
            });
    
    
    
        }
    
        @Override
        protected void onDestroy() {
            Log.w("GetSignature", "onDestory");
            super.onDestroy();
        }
    
        private boolean captureSignature() {
    
            boolean error = false;
            String errorMessage = "";
    
    
     //       if(yourName.getText().toString().equalsIgnoreCase("")){
     //           errorMessage = errorMessage + "Please enter your Name\n";
     //           error = true;
     //       }   
    
            if(error){
                Toast toast = Toast.makeText(this, errorMessage, Toast.LENGTH_SHORT);
                toast.setGravity(Gravity.TOP, 105, 50);
                toast.show();
            }
    
            return error;
        }
    
        private String getTodaysDate() { 
    
            final Calendar c = Calendar.getInstance();
            int todaysDate =     (c.get(Calendar.YEAR) * 10000) + 
            ((c.get(Calendar.MONTH) + 1) * 100) + 
            (c.get(Calendar.DAY_OF_MONTH));
            Log.w("DATE:",String.valueOf(todaysDate));
            return(String.valueOf(todaysDate));
    
        }
    
        private String getCurrentTime() {
    
            final Calendar c = Calendar.getInstance();
            int currentTime =     (c.get(Calendar.HOUR_OF_DAY) * 10000) + 
            (c.get(Calendar.MINUTE) * 100) + 
            (c.get(Calendar.SECOND));
            Log.w("TIME:",String.valueOf(currentTime));
            return(String.valueOf(currentTime));
    
        }
    
    
    
        public class signature extends View 
        {
            private static final float STROKE_WIDTH = 5f;
            private static final float HALF_STROKE_WIDTH = STROKE_WIDTH / 2;
            private Paint paint = new Paint();
            private Path path = new Path();
    
            private float lastTouchX;
            private float lastTouchY;
            private final RectF dirtyRect = new RectF();
    
            public signature(Context context, AttributeSet attrs) 
            {
                super(context, attrs);
                paint.setAntiAlias(true);
                paint.setColor(Color.BLACK);
                paint.setStyle(Paint.Style.STROKE);
                paint.setStrokeJoin(Paint.Join.ROUND);
                paint.setStrokeWidth(STROKE_WIDTH);
            }
    
            public void save(View v) 
            {
                Log.v("log_tag", "Width: " + v.getWidth());
                Log.v("log_tag", "Height: " + v.getHeight());
                if(mBitmap == null)
                {
                    mBitmap =  Bitmap.createBitmap (mContent.getWidth(), mContent.getHeight(), Bitmap.Config.RGB_565);;
                }
                Canvas canvas = new Canvas(mBitmap);
                v.draw(canvas);
    
                ByteArrayOutputStream bao = new ByteArrayOutputStream();
                mBitmap.compress(Bitmap.CompressFormat.PNG, 70, bao); 
                byte [] ba = bao.toByteArray();
    
                ba1=Base64.encodeToString(ba,Base64.DEFAULT);
                //Toast.makeText(SignatureCapture.this, ba1, Toast.LENGTH_LONG).show();
            }
    
            public void clear() 
            {
                path.reset();
                invalidate();
            }
    
            @Override
            protected void onDraw(Canvas canvas) 
            {
                canvas.drawPath(path, paint);
            }
    
            @Override
            public boolean onTouchEvent(MotionEvent event) 
            {
                float eventX = event.getX();
                float eventY = event.getY();
                mGetSign.setEnabled(true);
    
                switch (event.getAction()) 
                {
                case MotionEvent.ACTION_DOWN:
                    path.moveTo(eventX, eventY);
                    lastTouchX = eventX;
                    lastTouchY = eventY;
                    return true;
    
                case MotionEvent.ACTION_MOVE:
    
                case MotionEvent.ACTION_UP:
    
                    resetDirtyRect(eventX, eventY);
                    int historySize = event.getHistorySize();
                    for (int i = 0; i < historySize; i++) 
                    {
                        float historicalX = event.getHistoricalX(i);
                        float historicalY = event.getHistoricalY(i);
                        expandDirtyRect(historicalX, historicalY);
                        path.lineTo(historicalX, historicalY);
                    }
                    path.lineTo(eventX, eventY);
                    break;
    
                default:
                    debug("Ignored touch event: " + event.toString());
                    return false;
                }
    
                invalidate((int) (dirtyRect.left - HALF_STROKE_WIDTH),
                        (int) (dirtyRect.top - HALF_STROKE_WIDTH),
                        (int) (dirtyRect.right + HALF_STROKE_WIDTH),
                        (int) (dirtyRect.bottom + HALF_STROKE_WIDTH));
    
                lastTouchX = eventX;
                lastTouchY = eventY;
    
                return true;
            }
    
            private void debug(String string){
            }
    
            private void expandDirtyRect(float historicalX, float historicalY) 
            {
                if (historicalX < dirtyRect.left) 
                {
                    dirtyRect.left = historicalX;
                } 
                else if (historicalX > dirtyRect.right) 
                {
                    dirtyRect.right = historicalX;
                }
    
                if (historicalY < dirtyRect.top) 
                {
                    dirtyRect.top = historicalY;
                } 
                else if (historicalY > dirtyRect.bottom) 
                {
                    dirtyRect.bottom = historicalY;
                }
            }
    
            private void resetDirtyRect(float eventX, float eventY) 
            {
                dirtyRect.left = Math.min(lastTouchX, eventX);
                dirtyRect.right = Math.max(lastTouchX, eventX);
                dirtyRect.top = Math.min(lastTouchY, eventY);
                dirtyRect.bottom = Math.max(lastTouchY, eventY);
            }
        }
    }
    

    现在您只需要为新类定义布局:

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/linearLayout1"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >
    
    <LinearLayout android:layout_height="wrap_content"
        android:id="@+id/linearLayout2" android:layout_width="match_parent">
        <Button android:layout_height="50dp" android:layout_weight=".35"
            android:text="Clear" android:layout_width="0dp" android:id="@+id/clear" />
        <Button android:layout_height="50dp" android:layout_weight=".35"
            android:text="Save" android:layout_width="0dp" android:id="@+id/getsign" />
    </LinearLayout>
    <TableLayout android:layout_height="wrap_content"
        android:id="@+id/tableLayout1" android:layout_width="match_parent">
        <TableRow android:id="@+id/tableRow1" android:layout_width="wrap_content"
            android:layout_height="wrap_content">
        </TableRow>
        <TableRow android:id="@+id/tableRow3" android:layout_width="wrap_content"
            android:layout_height="wrap_content">
        </TableRow>
    </TableLayout>
    <LinearLayout android:layout_height="match_parent"
        android:id="@+id/linearLayout" android:layout_width="match_parent" />
    </LinearLayout>
    

    现在或多或少就是这样。请注意我不是编写活动代码的人,并且没有任何优点是我的,我只是简单地找到了一种如何以一种好的方式将它组合在一起的方法。

    我希望你们中的一些人会发现它很有用。