Android 触摸屏校准
话接上回,我们发现了手工利用 tslib 校验触摸屏的缺点。那么这一回 我们就来一次稍微高级一点的校验吧。
我们其实只需要相对的 x,y 以及lcd的 x,y 就可以把校验系数算出来。这里要说的是lcd的x,y是绝对的准确的 比如我们要在(50,50)画一个十字 那么这个50,50就是我们认为的绝对坐标。我们要的只是从android通过getX()和getY()拿到我们需要的相对坐标。
其实一切我们打算做的事情可以都在 InputDevice 里面做完
下面我把完整之后整个的 InputDevice 贴出来:
* * Copyright ( C) 2007 The Android Open Source Project * * Licensed under the Apache License, Version 2. 0 ( the "License" ) ; * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http: //www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND , either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * / package com. android. server ; import android. util . Log ; import android. view . Display; import android. view . MotionEvent; import android. view . Surface; import android. view . WindowManagerPolicy; import java . io . FileInputStream ; import java . util . StringTokenizer ; import java . io . File ; import java . io . FileNotFoundException ; import java . io . FileOutputStream ; import java . io . IOException ; public class InputDevice { /** Amount that trackball needs to move in order to generate a key event. */ static final int TRACKBALL_MOVEMENT_THRESHOLD = 6; //once edit static final String CALIBRATION_FILE = "/data/etc/pointercal" ; //edit ends final int id ; final int classes; final String name ; final AbsoluteInfo absX; final AbsoluteInfo absY; final AbsoluteInfo absPressure; final AbsoluteInfo absSize; //once edit static TransformInfo tInfo; //edit ends long mDownTime = 0; int mMetaKeysState = 0; static File desFile; final MotionState mAbs = new MotionState( 0, 0) ; final MotionState mRel = new MotionState( TRACKBALL_MOVEMENT_THRESHOLD, TRACKBALL_MOVEMENT_THRESHOLD) ; static class MotionState { int xPrecision; int yPrecision; float xMoveScale; float yMoveScale; MotionEvent currentMove = null ; boolean changed = false; boolean down = false; boolean lastDown = false; long downTime = 0; int x = 0; int y = 0; int pressure = 1; int size = 0; MotionState( int mx, int my) { xPrecision = mx; yPrecision = my; xMoveScale = mx ! = 0 ? ( 1. 0f/ mx) : 1. 0f; yMoveScale = my ! = 0 ? ( 1. 0f/ my) : 1. 0f; } MotionEvent generateMotion( InputDevice device, long curTime, boolean isAbs, Display display, int orientation, int metaState) { if ( ! changed) { return null ; } //once edit String prop = System . getProperty ( "ts.config.calibrate" , "noset" ) ; if ( prop. equalsIgnoreCase ( "start" ) ) { Log . i( "XXW prop" , prop) ; Log . i( "XXW" , "prop.equalsIgnoreCase start" ) ; device. tInfo = null ; } else if ( prop. equalsIgnoreCase ( "done" ) ) { Log . i( "XXW prop" , prop) ; Log . i( "XXW" , "prop.equalsIgnoreCase done" ) ; readCalibrate( ) ; System . setProperty ( "ts.config.calibrate" , "end" ) ; } else { Log . i( "XXW prop" , prop) ; Log . i( "XXW" , "prop.equalsIgnoreCase else" ) ; } //edit ends float scaledX = x; float scaledY = y; float temp; float scaledPressure = 1. 0f; float scaledSize = 0; int edgeFlags = 0; if ( isAbs) { int w = display. getWidth ( ) - 1; int h = display. getHeight ( ) - 1; if ( orientation = = Surface. ROTATION_90 | | orientation = = Surface. ROTATION_270) { int tmp = w; w = h; h = tmp; } if ( device. absX ! = null ) { //once edit if ( device. tInfo ! = null ) { scaledX = ( device. tInfo. x1 * x + device. tInfo. y1 * y + device. tInfo. z1) / device. tInfo. s; Log . i( "XXW" , "x: " + x) ; Log . i( "XXW" , "trans x: " + scaledX) ; } else //edit ends scaledX = ( ( scaledX- device. absX. minValue) / device. absX. range) * w; } if ( device. absY ! = null ) { //once edit if ( device. tInfo ! = null ) { scaledY = ( device. tInfo. x2 * x + device. tInfo. y2 * y + device. tInfo. z2) / device. tInfo. s; Log . i( "XXW" , "y: " + y) ; Log . i( "XXW" , "trans y: " + scaledY) ; } else //edit ends scaledY = ( ( scaledY- device. absY. minValue) / device. absY. range) * h; } if ( device. absPressure ! = null ) { scaledPressure = ( ( pressure- device. absPressure. minValue) / ( float ) device. absPressure. range) ; } if ( device. absSize ! = null ) { scaledSize = ( ( size - device. absSize. minValue) / ( float ) device. absSize. range) ; } switch ( orientation) { case Surface. ROTATION_90: temp = scaledX; scaledX = scaledY; scaledY = w- temp; break ; case Surface. ROTATION_180: scaledX = w- scaledX; scaledY = h- scaledY; break ; case Surface. ROTATION_270: temp = scaledX; scaledX = h- scaledY; scaledY = temp; break ; } if ( scaledX = = 0) { edgeFlags + = MotionEvent. EDGE_LEFT; } else if ( scaledX = = display. getWidth ( ) - 1. 0f) { edgeFlags + = MotionEvent. EDGE_RIGHT; } if ( scaledY = = 0) { edgeFlags + = MotionEvent. EDGE_TOP; } else if ( scaledY = = display. getHeight ( ) - 1. 0f) { edgeFlags + = MotionEvent. EDGE_BOTTOM; } } else { scaledX * = xMoveScale; scaledY * = yMoveScale; switch ( orientation) { case Surface. ROTATION_90: temp = scaledX; scaledX = scaledY; scaledY = - temp; break ; case Surface. ROTATION_180: scaledX = - scaledX; scaledY = - scaledY; break ; case Surface. ROTATION_270: temp = scaledX; scaledX = - scaledY; scaledY = temp; break ; } } changed = false; if ( down ! = lastDown) { int action ; lastDown = down; if ( down) { action = MotionEvent. ACTION_DOWN; downTime = curTime; } else { action = MotionEvent. ACTION_UP; } currentMove = null ; if ( ! isAbs) { x = y = 0; } return MotionEvent. obtain( downTime, curTime, action , scaledX, scaledY, scaledPressure, scaledSize, metaState, xPrecision, yPrecision, device. id , edgeFlags) ; } else { if ( currentMove ! = null ) { if ( false) Log . i( "InputDevice" , "Adding batch x=" + scaledX + " y=" + scaledY + " to " + currentMove) ; currentMove. addBatch ( curTime, scaledX, scaledY, scaledPressure, scaledSize, metaState) ; if ( WindowManagerPolicy. WATCH_POINTER) { Log . i( "KeyInputQueue" , "Updating: " + currentMove) ; } return null ; } MotionEvent me = MotionEvent. obtain( downTime, curTime, MotionEvent. ACTION_MOVE, scaledX, scaledY, scaledPressure, scaledSize, metaState, xPrecision, yPrecision, device. id , edgeFlags) ; currentMove = me; return me; } } } static class AbsoluteInfo { int minValue; int maxValue; int range; int flat; int fuzz; } ; //once edit static class TransformInfo { float x1; float y1; float z1; float x2; float y2; float z2; float s; } ; //edit ends InputDevice( int _id, int _classes, String _name, AbsoluteInfo _absX, AbsoluteInfo _absY, AbsoluteInfo _absPressure, AbsoluteInfo _absSize) { id = _id; classes = _classes; name = _name; absX = _absX; absY = _absY; absPressure = _absPressure; absSize = _absSize; //once edit desFile = new File ( CALIBRATION_FILE) ; readCalibrate( ) ; //edit ends } static void readCalibrate( ) { //xxw added Log . i( "XXW" , "readCalibrate!" ) ; TransformInfo t = null ; try { FileInputStream is = new FileInputStream ( CALIBRATION_FILE) ; byte [ ] mBuffer = new byte [ 64] ; int len = is. read ( mBuffer) ; is. close ( ) ; if ( len > 0) { int i; for ( i = 0 ; i < len ; i+ + ) { if ( mBuffer[ i] = = '/n' | | mBuffer[ i] = = 0) { break ; } } len = i; } StringTokenizer st = new StringTokenizer ( new String ( mBuffer, 0, 0, len) ) ; t = new TransformInfo ( ) ; t. x1 = Integer . parseInt ( st. nextToken ( ) ) ; t. y1 = Integer . parseInt ( st. nextToken ( ) ) ; t. z1 = Integer . parseInt ( st. nextToken ( ) ) ; t. x2 = Integer . parseInt ( st. nextToken ( ) ) ; t. y2 = Integer . parseInt ( st. nextToken ( ) ) ; t. z2 = Integer . parseInt ( st. nextToken ( ) ) ; t. s = Integer . parseInt ( st. nextToken ( ) ) ; } catch ( java . io . FileNotFoundException e) { Log . i( "XXW" , "FileNotFound!" ) ; } catch ( java . io . IOException e) { Log . i( "XXW" , "IOException" ) ; } tInfo = t; Log . i( "XXW" , "readCalibrate done!" ) ; } } ; |
与上一次的那个 InputDevice 相比 我将读取校准文件的代码单独的变成一个函数,之所以这么做 是因为我们打算不重启就可以直接让 android 校准完成。 这 里其实也没什么东西 只是读取校验文件 如果读取成功了就用校验公式计算出校准后的坐标。 为了避免重启 所以用了一个系统属性ts.config.calibrate来决定重新读取一次文件。当然 当ts.config.calibrate值表明正在校验的话 就直接传上来点击的原始坐标而不经过换算。校验完成之后读取一次校验文件 然后将系统属性变成其他值不再读取文件。
下面我们就要写一个 apk 来实现校准了。
这里 我尝试了 2 种方法 一种是纯 java 的 apk 一种是 jni 的 apk 。其实对于校准来说 上层已经能拿到 x,y 那么我们取 5 个点就已经可以算出来那 7 个校准值 然后利用 java 存文件了。整个 apk 的java代码
package com. android. calibrate; import java . io . File ; import java . io . FileNotFoundException ; import java . io . FileOutputStream ; import java . io . IOException ; import android. util . Log ; public class Calibrate { private calibration cal; public Calibrate( ) { cal = new calibration( ) ; } class calibration { int x[ ] = new int [ 5] ; int xfb[ ] = new int [ 5] ; int y[ ] = new int [ 5] ; int yfb[ ] = new int [ 5] ; int a[ ] = new int [ 7] ; } ; boolean perform_calibration( ) { Log . i( "XXW" , "perform_calibration" ) ; int j; float n, x, y, x2, y2, xy, z, zx, zy; float det, a, b, c, e, f, i; float scaling = ( float ) 65536. 0; // Get sums for matrix n = x = y = x2 = y2 = xy = 0; for ( j = 0; j < 5; j+ + ) { n + = 1. 0; x + = ( float ) cal. x[ j] ; y + = ( float ) cal. y[ j] ; x2 + = ( float ) ( cal. x[ j] * cal. x[ j] ) ; y2 + = ( float ) ( cal. y[ j] * cal. y[ j] ) ; xy + = ( float ) ( cal. x[ j] * cal. y[ j] ) ; } // Get determinant of matrix -- check if determinant is too small det = n * ( x2 * y2 - xy * xy) + x * ( xy * y - x * y2) + y * ( x * xy - y * x2) ; if ( det < 0. 1 & & det > - 0. 1) { Log . i( "ts_calibrate: determinant is too small -- %f/n" , "" + det) ; return false; } // Get elements of inverse matrix a = ( x2 * y2 - xy * xy) / det; b = ( xy * y - x * y2) / det; c = ( x * xy - y * x2) / det; e = ( n * y2 - y * y) / det; f = ( x * y - n * xy) / det; i = ( n * x2 - x * x) / det; // Get sums for x calibration z = zx = zy = 0; for ( j = 0; j < 5; j+ + ) { z + = ( float ) cal. xfb[ j] ; zx + = ( float ) ( cal. xfb[ j] * cal. x[ j] ) ; zy + = ( float ) ( cal. xfb[ j] * cal. y[ j] ) ; } // Now multiply out to get the calibration for framebuffer x coord cal. a[ 0] = ( int ) ( ( a * z + b * zx + c * zy) * ( scaling) ) ; cal. a[ 1] = ( int ) ( ( b * z + e * zx + f * zy) * ( scaling) ) ; cal. a[ 2] = ( int ) ( ( c * z + f * zx + i * zy) * ( scaling) ) ; System . out. printf ( "%f %f %f/n" , ( a * z + b * zx + c * zy) , ( b * z + e * zx + f * zy) , ( c * z + f * zx + i * zy) ) ; // Get sums for y calibration z = zx = zy = 0; for ( j = 0; j < 5; j+ + ) { z + = ( float ) cal. yfb[ j] ; zx + = ( float ) ( cal. yfb[ j] * cal. x[ j] ) ; zy + = ( float ) ( cal. yfb[ j] * cal. y[ j] ) ; } // Now multiply out to get the calibration for framebuffer y coord cal. a[ 3] = ( int ) ( ( a * z + b * zx + c * zy) * ( scaling) ) ; cal. a[ 4] = ( int ) ( ( b * z + e * zx + f * zy) * ( scaling) ) ; cal. a[ 5] = ( int ) ( ( c * z + f * zx + i * zy) * ( scaling) ) ; System . out. printf ( "%f %f %f/n" , ( a * z + b * zx + c * zy) , ( b * z + e * zx + f * zy) , ( c * z + f * zx + i * zy) ) ; // If we got here, we're OK, so assign scaling to a[6] and return cal. a[ 6] = ( int ) scaling; return true; /* * // This code was here originally to just insert default values * for(j=0;j<7;j++) { c->a[j]=0; } c->a[1] = c->a[5] = c->a[6] = 1; * return 1; */ } void get_sample( int index, int x1, int y1, int x, int y) { Log . i( "XXW" , "get_sample" ) ; cal. x[ index] = x1; cal. y[ index] = y1; cal. xfb[ index] = x; cal. yfb[ index] = y; } int calibrate_main( ) { int result = 0; Log . i( "XXW" , "calibrate_main" ) ; if ( perform_calibration( ) ) { String strPara = String . format ( "%d %d %d %d %d %d %d" , cal. a[ 1] , cal. a[ 2] , cal. a[ 0] , cal. a[ 4] , cal. a[ 5] , cal. a[ 3] , cal. a[ 6] ) ; boolean success = new File ( "/data/etc" ) . mkdir ( ) ; if ( ! success) { Log . i( this . toString ( ) , "no success" ) ; } File desFile = new File ( "/data/etc/pointercal" ) ; if ( ! desFile. exists ( ) ) try { desFile. createNewFile ( ) ; } catch ( IOException e1) { e1. printStackTrace ( ) ; } FileOutputStream fos; try { fos = new FileOutputStream ( desFile) ; byte [ ] buf = strPara. getBytes ( ) ; int bytesRead = buf. length ; try { fos. write ( buf, 0, bytesRead) ; fos. flush ( ) ; fos. close ( ) ; } catch ( IOException e) { e. printStackTrace ( ) ; } } catch ( FileNotFoundException e) { e. printStackTrace ( ) ; } result = 0; } else { result = - 1; } return result ; } } |
其实这个就是 tslib 里那个 ts_calibrate.cpp 的姐妹篇。重要的函数就一个 perform_calibration() 。这里要注意的是 你必须首先利用 get_sample 读取 5 个点的值来初始化 cal 然后再调用 calibrate_main 来计算校验系数。而且那 5 个点的顺序为 左上 右上 右下 左下 中间。这 4 个点基本上应该在屏幕的边缘附近 否则计算出来的校验值可能不准。 利用上面的那个类 写一个 apk 出来跑跑看 流程应该就可以了。
package com. android. calibrate; import com. android. calibrate. R; import java . util . ArrayList ; import java . util . List ; import android. app. Activity ; import android. os. Bundle; import android. util . Log ; import android. view . MotionEvent; import android. view . View ; import android. view . Window ; import android. view . WindowManager; import android. view . View . OnTouchListener; import android. widget. ImageView ; import android. widget. Toast; import android. os. SystemProperties; public class AndroidCalibrate extends Activity { private Calibrate cal; private int time ; private List < ImageView > list = new ArrayList < ImageView > ( ) ; int xList[ ] = { 50, 270, 270, 50, 160 } ; int yList[ ] = { 50, 350, 350, 50, 200 } ; static void setNotTitle( Activity act) { act. requestWindowFeature( Window . FEATURE_NO_TITLE) ; } static void setFullScreen( Activity act) { setNotTitle( act) ; act. getWindow ( ) . setFlags( WindowManager. LayoutParams. FLAG_FULLSCREEN, WindowManager. LayoutParams. FLAG_FULLSCREEN) ; } public void onCreate( Bundle savedInstanceState) { super . onCreate( savedInstanceState) ; setFullScreen( this ) ; this . setContentView( R. layout . main) ; //System.setProperty("ts.config.calibrate", "start"); //String prop = System.getProperty("ts.config.calibrate", null); SystemProperty. set ( "ts.config.calibrate" , "start" ) ; ImageView image0 = ( ImageView ) findViewById( R. id . crossview_topleft) ; ImageView image1 = ( ImageView ) findViewById( R. id . crossview_topright) ; ImageView image2 = ( ImageView ) findViewById( R. id . crossview_bottomright) ; ImageView image3 = ( ImageView ) findViewById( R. id . crossview_bottomleft) ; ImageView image4 = ( ImageView ) findViewById( R. id . crossview_center) ; image1. setVisibility( View . INVISIBLE) ; image2. setVisibility( View . INVISIBLE) ; image3. setVisibility( View . INVISIBLE) ; image4. setVisibility( View . INVISIBLE) ; list . clear ( ) ; list . add ( image0) ; list . add ( image1) ; list . add ( image2) ; list . add ( image3) ; list . add ( image4) ; for ( ImageView image : list ) { image . setOnTouchListener( this . onTouchListener) ; } cal = new Calibrate( ) ; time = 0; } OnTouchListener onTouchListener = new OnTouchListener( ) { public boolean onTouch( View v, MotionEvent event ) { Log . i( "Property" , SystemProperty. get ( "ts.config.calibrate" , "noset" ) ) ; if ( event . getAction ( ) = = MotionEvent. ACTION_DOWN) { Log . i( "XXW" , "onTouchListener" ) ; v. setVisibility( View . INVISIBLE) ; time = list . indexOf ( ( ImageView ) v) ; if ( time < 5) { if ( time + 1 < 5) list . get ( time + 1) . setVisibility( View . VISIBLE) ; Log . i( "XXW time onTouchListener" , " " + time ) ; cal. get_sample( time , ( int ) event . getX ( ) , ( int ) event . getY ( ) , xList[ time ] , yList[ time ] ) ; if ( time = = 4) { Log . i( "XXW" , "calibrate_main" ) ; cal. calibrate_main( ) ; Toast. makeText( getBaseContext( ) , "Calibrate Done!" , Toast. LENGTH_SHORT) . show ( ) ; //System.setProperty("ts.config.calibrate", "done"); SystemProperty. set ( "ts.config.calibrate" , "done" ) ; AndroidCalibrate. this . finish ( ) ; } } } return false; } } ; } |
| |