精华内容
下载资源
问答
  • 首先讲强制横屏和竖屏,其实很少App需要强制转屏的,一般在视频播放,相机这类需要旋转屏幕 (1)这段代码是直接横屏 (interfaceOrientation 这个枚举有各种情况) // 视图显示为横屏状态 - (BOOL)...

    首先讲强制横屏和竖屏,其实很少App需要强制转屏的,一般在视频播放,相机这类需要旋转屏幕

    (1)这段代码是直接横屏 (interfaceOrientation 这个枚举有各种情况

    // 视图显示为横屏状态
    - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
        return (interfaceOrientation == UIInterfaceOrientationLandscapeLeft);
    }
    -(BOOL)shouldAutorotate {
        return YES;
    }
    -(UIInterfaceOrientationMask)supportedInterfaceOrientations {
        return UIInterfaceOrientationMaskLandscapeLeft;
    }
    

    (2)一般情况可以使用通知来做,但是在屏幕禁止旋转的时候通知也是没效果的,可以参考这个很详细:IOS UIDevice & IOS检测屏幕旋转实例 这里有详细的说明获取真机的信息

    (3)所以在屏幕禁止旋转的时候我们只能强制旋转的,

    这是手动强制旋转的

    // 强制旋转
    - (IBAction)tranfromOnClick:(UIButton *)sender
    {
        if ([UIDevice currentDevice].orientation == UIDeviceOrientationPortrait)
        {
            [[UIDevice currentDevice] setValue:[NSNumber numberWithInteger:UIDeviceOrientationPortrait] forKey:@"orientation"];//这句话是防止手动先把设备置为横屏,导致下面的语句失效.
            [[UIDevice currentDevice] setValue:[NSNumber numberWithInteger:UIDeviceOrientationLandscapeLeft] forKey:@"orientation"];
        } else {
            [[UIDevice currentDevice] setValue:[NSNumber numberWithInteger:UIDeviceOrientationLandscapeLeft] forKey:@"orientation"];//这句话是防止手动先把设备置为竖屏,导致下面的语句失效.
            [[UIDevice currentDevice] setValue:[NSNumber numberWithInteger:UIDeviceOrientationPortrait] forKey:@"orientation"];
        }
    }
    之后是要再强制的情况下手动旋转,当我们横着的时候屏幕也要旋转到横屏,竖屏的时候屏幕也要旋转到竖屏,这里我们就只能用  螺旋仪和加速器 来判断了

    #import <CoreMotion/CoreMotion.h> 首先加入这么框架

    创建两个对象
    @property (strong, nonatomic) CMMotionManager *motionManager;
    
    @property (assign, nonatomic) BOOL isHeng;   // 判断横竖屏

    /**
     *  在真机关闭屏幕旋转功能时如何去判断屏幕方向
     */
    - (void)initMotionManager
    {
        if (_motionManager == nil) {
            _motionManager = [[CMMotionManager alloc] init];
        }
        
        // 提供设备运动数据到指定的时间间隔
        _motionManager.deviceMotionUpdateInterval = .3;
        
        if (_motionManager.deviceMotionAvailable) {  // 确定是否使用任何可用的态度参考帧来决定设备的运动是否可用
            // 启动设备的运动更新,通过给定的队列向给定的处理程序提供数据。
            [_motionManager startDeviceMotionUpdatesToQueue:[NSOperationQueue mainQueue] withHandler:^(CMDeviceMotion *motion, NSError *error) {
                
                [self performSelectorOnMainThread:@selector(handleDeviceMotion:) withObject:motion waitUntilDone:YES];
            }];
        }else
        {
            [self setMotionManager:nil];
        }
    }

    - (void)handleDeviceMotion:(CMDeviceMotion *)deviceMotion
    {
        double x = deviceMotion.gravity.x;
        double y = deviceMotion.gravity.y;
        
        if (fabs(y) >= fabs(x))
        {  // NSLog(@"竖屏");
            if (self.isHeng == YES) {
                [[UIDevice currentDevice] setValue:[NSNumber numberWithInteger:UIDeviceOrientationLandscapeLeft] forKey:@"orientation"];//这句话是防止手动先把设备置为竖屏,导致下面的语句失效.
                [[UIDevice currentDevice] setValue:[NSNumber numberWithInteger:UIDeviceOrientationPortrait] forKey:@"orientation"];
                self.isHeng = NO;
            }
        }
        else
        {       // NSLog(@"横屏");
            if (self.isHeng == NO) {
                [[UIDevice currentDevice] setValue:[NSNumber numberWithInteger:UIDeviceOrientationPortrait] forKey:@"orientation"];//这句话是防止手动先把设备置为横屏,导致下面的语句失效.
                [[UIDevice currentDevice] setValue:[NSNumber numberWithInteger:UIDeviceOrientationLandscapeLeft] forKey:@"orientation"];
                self.isHeng = YES;
            }
        }
    }

    源码: 下载源码

    展开全文
  • Android 今日头条屏幕适配详细使用攻略

    千次阅读 多人点赞 2020-09-22 09:33:40
    首先感谢大神JessYan的创神之作《AndroidAutoSize》,大神以今日头条屏幕适配的核心代码为基础进行了扩展封装,产生了《AndroidAutoSize》这个能快速接入使用屏幕适配方案,这个屏幕适配方案是我遇到的截止2020.9....

    Android 第三方库系列文章

    1. Android 今日头条屏幕适配详细使用攻略
    2. Lottie动画 轻松使用

    博客创建时间:2020.09.20
    博客更新时间:2021.06.27

    以Android studio build=4.2.1,gradle=6.7.1,SdkVersion 30来分析讲解。如图文和网上其他资料不一致,可能是别的资料版本较低而已


    前言

    首先感谢大神JessYan的创神之作《AndroidAutoSize》,大神以今日头条屏幕适配的核心代码为基础进行了扩展封装,产生了《AndroidAutoSize》这个能快速接入使用的屏幕适配方案,这个屏幕适配方案是我遇到的截止2020.9.15为止最强大、简单有效的屏幕适配方案。我已使用该方案有一年,在使用过程未发现有何问题,强烈推荐各位极客们使用学习。

    以下是大神JessYan的相关地址:

    大神的源码都在github中各位可以自行下载,我写这篇博客的目的就是记录使用心得,并将该框架的重要类和方法使用进行详细说明,大神的文章中对细节问题写的比较少,我对其进行了细微的修改和详细的注解解释。我自己修改注释过得框架源码请前往github下载https://github.com/l424533553/MyAutoSize


    1. 屏幕像素

    像素
    通常所说的像素,就是CCD/CMOS上光电感应元件的数量,一个感光元件经过感光,光电信号转换,A/D转换等步骤以后,在输出的照片上就形成一个点,我们如果把影像放大数倍,会发现这些连续色调其实是由许多色彩相近的小方点所组成,这些小方点就是构成影像的最小单位“像素”(Pixel)。简而言之,像素就是手机屏幕的最小构成单元。

    屏幕尺寸
    屏幕尺寸指屏幕的对角线的长度,单位是英寸,1英寸=2.54厘米。比如常见的屏幕尺寸有2.4、2.8、3.5、3.7、4.2、5.0、5.5、6.0等

    屏幕分辨率
    屏幕分辨率是指在横纵向上的像素点数,单位是px,1px=1个像素点。一般以纵向像素横向像素,如19201080

    屏幕像素密度(dpi)
    屏幕像素密度是指每英寸上的像素点数,单位是dpi,即“dot per inch”的缩写。屏幕像素密度与屏幕尺寸和屏幕分辨率有关,在单一变化条件下,屏幕尺寸越小、分辨率越高,像素密度越大,反之越小。
    在这里插入图片描述

    计算公式: 像素密度 = 像素 / 尺寸 (dpi = px / in)
    标准屏幕像素密度(mdpi): 每英寸长度上还有160个像素点(160dpi),即称为标准屏幕像素密度(mdpi)。

    密度无关像素(dp)
    含义:density-independent pixel,叫dp或dip,与终端上的实际物理像素点无关
    单位:dp,可以保证在不同屏幕像素密度的设备上显示相同的效果,是安卓特有的长度单位。
    场景例子:假如同样都是画一条长度是屏幕一半的线,如果使用px作为计量单位,那么在480x800分辨率手机上设置应为240px;在320x480的手机上应设置为160px,二者设置就不同了;如果使用dp为单位,在这两种分辨率下,160dp都显示为屏幕一半的长度。
    dp与px的转换:1dp = (dpi / 160 ) * 1px;

    密度类型代表的分辨率(px)屏幕像素密度(dpi)换算
    低密度(ldpi)240 x 3201201dp = 0.75px
    中密度(mdpi)320 x 4801601dp = 1px
    高密度(hdpi)480 x 8002401dp = 1.5px
    超高密度(xhdpi)720 x 12803201dp = 2px
    超超高密度(xxhdpi)1080 x 19204801dp = 3px

    独立比例像素(sp)
    scale-independent pixel,叫sp或sip,字体大小专用单位 ,Android开发时用此单位设置文字大小,可根据字体大小首选项进行缩放。
    推荐使用12sp、14sp、18sp、22sp作为字体大小,不推荐使用奇数和小数,容易造成精度丢失,12sp以下字体太小。

    sp与dp的区别
    dp只跟屏幕的像素密度有关, sp和dp很类似但唯一的区别是,Android系统允许用户自定义文字尺寸大小(小、正常、大、超大等等),当文字尺寸是“正常”时1sp=1dp=0.00625英寸,而当文字尺寸是“大”""或“超大”时,1sp>1dp=0.00625英寸。类似我们在windows里调整字体尺寸以后的效果——窗口大小不变,只有文字大小改变。


    2. 适配原理

    传统的屏幕适配头如下几种措施:

    1. 种像素密度机型,做5套图。比例 1:1.5:2:3:4
    2. 多用相对布局
    3. 尺寸限定符
    4. 点九图
    5. 不同图片填充类型ScaleType
      但是以往的所有屏幕适配都有各种各样的问题和重大缺陷,直到字节跳动的屏幕适配方案出现。根据其公开的核心源码,网上重大大咖封装了各种屏幕适配框架,其中最成功且本人使用感受最好的是AutoSize框架。

    Android AutoSize的核心代码来源于字节跳动的微信文章https://mp.weixin.qq.com/s/d9QCoBP6kV9VSWvVldVVwA。网上也有多各个大神进行了代码的封装设计,都是万变不离其中。

    1. 核心思想

            DisplayMetrics appDisplayMetrics = application.getResources().getDisplayMetrics();
            if (sNoncompatDensity == 0) {
                sNoncompatDensity = appDisplayMetrics.density;
                sNoncompatDensity = appDisplayMetrics.scaledDensity;
                application.registerComponentCallbacks(new ComponentCallbacks() {
                    
                    public void onConfigurationChanged(Configuration newConfig) {
                        if (newConfig != null && newConfig.fontScale > 0) {
                            sNoncompatScaledDensity = application.getResources().getDisplayMetrics().scaledDensity;
                        }
                    }
    
                    
                    public void onLowMemory() {
    
                    }
                });
            }
            float targetDensity = appDisplayMetrics.widthPixels / 360;
            float targetScaleDensity = targetDensity * (sNoncompatScaledDensity / sNoncompatDensity);
            int targetDensityDpi = (int) (160 * targetDensity);
            appDisplayMetrics.density = targetDensity;
            appDisplayMetrics.scaledDensity = targetScaleDensity;
            appDisplayMetrics.densityDpi = targetDensityDpi;
    
            final DisplayMetrics activityDisplayMetrics = activity.getResources().getDisplayMetrics();
            activityDisplayMetrics.density = targetDensity;
            activityDisplayMetrics.scaledDensity = targetScaleDensity;
            activityDisplayMetrics.densityDpi = targetDensityDpi;
    

    原理很简单,例如一个4.59的10801920的手机它的dpi=480,它的density=480/160,则说明1dp=3px。当我们在布局中给如TextView设置layout_width=30dp时,在程序运行时会自动计算其对应px单位长度90px,px=dpdensity。

    今日头条适配方案的核心就是动态计算程序中的density=appDisplayMetrics.widthPixels / 360,360是原始设计图纸的dp。假设原先的设计图纸10801920,现在适配5.99寸560dpi的14402880手机,则30dp=30560/160=105px,实际上屏幕适配要求的30dp=1440/36030=120px才可以达到适配效果。因为120/1440=90/1080,控件在布局中的占宽比是一样的才能达到宽度适配效果。这就是为什么要动态修改全局或activity的DisplayMetrics#density的目的了。


    2. 优缺点

    • 优点:
    1. 侵入性非常低,该方案和项目完全解耦,使用的还是Android官方单位
    2. 接入无性能损耗,使用的全是Android官方的API。
    • 缺点:
    1. 项目中的系统控件、三方库控件、等非我们项目自身设计的控件,它们的设计图尺寸并不会和我们项目自身的设计图尺寸一样,此时会产生适配误差。解决方案就是取消当前 Activity 的适配效果,改用其他的适配方案
    2. 系统修改字体大小后,返回应用系统字体大小还是未改变,需要设置registerComponentCallbacks监听。 Android AutoSize框架已经解决了该问题。
    3. 在使用过程中需要进行registerComponentCallbacks监听内容文字的大小改变情况,解决退出应用修改文字大小后,文字大小不改变的情况。

    3. 框架配置

    依赖配置

    1. 远程依赖,截止到2020.9.15,版本为1.2.1
        implementation 'me.jessyan:autosize:1.2.1'
    
    1. 从github上下载源码进行library库依赖

    参数配置
    在AndroidManifest.xml中配置参数

    <manifest>
        <application>    
            ...
            <meta-data
                android:name="design_width_in_dp"
                android:value="360"/>
            <meta-data
                android:name="design_height_in_dp"
                android:value="640"/>      
            ...
         </application>           
    </manifest>
    

    4. 自定义初始化

    本文中使用的框架是经过大神JessYan的封装后成为你所看到的框架。它能根据一套给定的设计图尺寸进行布局展示,当安装当不同分辨率尺寸的设备上时,它能自动适配屏幕。

    框架的初始化时机是配置在ContentProvider中,在Application#onCreate()方法之前启动。框架一旦初始化完成,其适配效果会在Activity和Fragment、各种View中自动全局适配。程序将默认是以屏幕宽度为基准进行适配的,并且使用的是在AndroidManifest中填写的全局设计图尺寸进行全局适配。

    框架支持dp、sp两个主单位,pt、in、mm三个冷门副单位,如果使用副单位,可以规避系统控件或三方库控件使用的不良影响。

    ContentProvider初始化第三方库
    ContentProvider是一种共享型组件,它通过Binder向其他组件或者其他应用程序提供数据,当ContentProvider所在进程启动时候,ContentProvider会被同时启动并被发布到AMS中。

    ContentProvider的onCreate要优先于Application的onCreate,但在attachBaseContext()之后而执行,它的具体详细启动源码在ActivityThread中。很多人会在ContentProvider#onCreate()初始化第三方库。

    一般进行了依赖配置参数配置两操作,Android AutoSize就配置完成可以直接使用了,它的框架源码初始化在InitProvider代码中。

    在InitProvider 中已进行了初始化设置

    public class InitProvider extends ContentProvider {
        @Override
        public boolean onCreate() {
            if (getContext() != null) {
                Context application = getContext().getApplicationContext();
                if (application == null) {
                    application = AutoSizeUtils.getApplicationByReflect();
                }
                AutoSizeConfig.getInstance()
                        .setLog(true)
                        .init((Application) application)
                        .setUseDeviceSize(false);
                return true;
            }
            return false;
        }
    

    但是为了个性化的配置,我们可以在Application中进行一些自定义设置,设置的方法都应写在Application#onCreate()方法中。

    public class Application {
        @Override
        public void onCreate() {
            super.onCreate();
            ...
            AutoSize.initCompatMultiProcess(this);
            AutoSize.checkAndInit(this);
            AutoSizeConfig.getInstance()
                    .setCustomFragment(true)
                    .setExcludeFontScale(true)
                    .setPrivateFontScale(0.8f)
                    .setLog(false)
                    .setBaseOnWidth(true)
                    .setUseDeviceSize(true)
                    //屏幕适配监听器
                    .setOnAdaptListener(new OnAdaptListener() {
                        @Override
                        public void onAdaptBefore(Object target, Activity activity) {
    //                        AutoSizeConfig.getInstance().setScreenWidth(ScreenUtils.getScreenSize(activity)[0]);
    //                        AutoSizeConfig.getInstance().setScreenHeight(ScreenUtils.getScreenSize(activity)[1]);
                            AutoSizeLog.d(String.format(Locale.ENGLISH, "%s onAdaptBefore!", target.getClass().getName()));
                        }
    
                        @Override
                        public void onAdaptAfter(Object target, Activity activity) {
                            AutoSizeLog.d(String.format(Locale.ENGLISH, "%s onAdaptAfter!", target.getClass().getName()));
                        }
                    });
            configUnits();
        }
        
        private void configUnits() {
            AutoSizeConfig.getInstance()
                    .getUnitsManager()
                    .setSupportDP(true)
                    .setDesignSize(2160, 3840)
                    .setSupportSP(true)
                    .setSupportSubunits(Subunits.MM);
        }
    }
    

    5. 常用方法解析

    对于初始化中方法,我们进行一一分析
    1. AutoSize.initCompatMultiProcess(Context context)
    当 App 中出现多进程,并且您需要适配所有的进程,就需要在 App 初始化时调用。一般的单进程App程序不用设置。

    2. AutoSize.checkAndInit(Application application)

         if (!checkInit()) {
                AutoSizeConfig.getInstance()
                        .setLog(true)
                        .init(application)
                        .setUseDeviceSize(false);
            }
    

    一般来说Android AutoSize会通过InitProvider实例化自动完成初始化,是不需要调用checkAndInit()方法的。但由于某些 issues 反应, 可能会在某些特殊情况下出现InitProvider未能正常实例化的情况, 导致 AndroidAutoSize 未能完成初始化。所以需要使用该方法确保Android AutoSize 初始化成功。

    3. AutoSizeConfig.getInstance().setCustomFragment(boolean customFragment)
    设定是否让框架支持自定义Fragment 的适配参数,一般这个需求比较少。默认不支持的

    4. AutoSizeConfig.getInstance().setExcludeFontScale(true)
    是否屏蔽系统字体大小对AndroidAutoSize 的影响, 如果为 true, App 内的字体的大小将不会跟随系统设置中字体大小的改变, 如果为 false, 则会跟随系统设置中字体大小的改变, 默认为 false

    5. AutoSizeConfig.getInstance().setPrivateFontScale(float fontScale)
    区别于系统字体大小的放大比例, AndroidAutoSize 允许 APP 内部可以独立于系统字体大小之外,独自拥有全局调节 APP 字体大小的能力。 fontScale取值0~1,设为 0 则取消此功能。同时字体的单位必须是sp做单位。

    6. AutoSizeConfig.getInstance().setLog(boolean log)
    设置是否打印AutoSize的日志,true为打印

    7. AutoSizeConfig.getInstance().setBaseOnWidth(true)
    是否全局按照宽度进行等比例适配,true以宽来适配,false以高来适配

    8. AutoSizeConfig.getInstance().stop(this)
    自动适配方案可以手动调用方法停止,需要注意的是Android AutoSize暂停只是停止了对后续还没有启动的{@link Activity}进行适配的工作,但对已经启动且已经适配的{@link Activity}不会有任何影响

    9. AutoSizeConfig.getInstance().restart()
    AutoSize可以暂停适配也可以重启适配,但是重启适配只能对后续还没有启动的 {@link Activity} 进行适配的工作,但对已经启动且在stop期间未适配的{@link Activity}不会有任何影响

    10. AutoSizeConfig.getInstance().setUseDeviceSize(true)
    是否以屏幕的实际尺寸为高度,默认为false,屏幕的适配高度是屏幕总高度减去状态栏高度。

    11. UnitsManager.setSupportSP(boolean supportSP)
    是否让框架支持sp单位,默认是为true支持,如果为false,则字体大小最好设置为其他单位才能自动适配

    12. UnitsManager.setSupportSubunits(Subunits supportSubunits)
    自主设置心仪的副单位,可以从pt、in、mm中进行选择,如果使用了Subunits#NONE即代表不支持副单位

    13. UnitsManager.setSupportDP(boolean supportDP)
    是否支持dp单位,默认是true支持,如果关闭将不对dp单位进行支持

    14. UnitsManager.setDesignSize(float designWidth, float designHeight)
    设置设计图尺寸,一般专为副单位尺寸设计,它与AndroidManifest.xml中配置的参数不一样,不会被覆盖。


    6. 常见接口及类的使用

    CustomAdapt
    实现CustomAdapt接口即可对activity和fragment进行新的自定义尺寸适配,适配方向可以自主选择是宽度还是高度。实现该接口会取消默认的适配方案和效果。对于fragment的自定义尺寸需要进AutoSizeConfig.getInstance().setCustomFragment(true)设置,默认是不支持对fragment的自定义尺寸适配的。

    <在CustomAdapt接口中需要实现者重写两个方法boolean isBaseOnWidth()和float getSizeInDp(),根据使用者需求自定义。

    • 1. boolean isBaseOnWidth()
      为了保证在高宽比不同的屏幕上也能正常适配,所以只能在宽度和高度之中选一个作为基准进行适配。 true为按照宽度适配, false 为按照高度适配

    • 2. float getSizeInDp()
      getSizeInDp 须配合isBaseOnWidth()使用, 有如下使用规则:
      如果 {@link #isBaseOnWidth()} 返回 {@code true}, {@link CustomAdapt #getSizeInDp} 则应该返回设计图的总宽度。
      如果 {@link #isBaseOnWidth()} 返回 {@code false}, {@link CustomAdapt #getSizeInDp} 则应该返回设计图的总高度。
      如果您不需要自定义设计图上的设计尺寸, 想继续使用在 AndroidManifest 中填写的设计图尺寸,getSizeInDp 则返回 0即可。

    CancelAdapt
    接口CancelAdapt没有任何成员变量,支持AndroidAutoSize的项目所有模块默认使用适配功能,第三方库的也不例外。
    如果某个页面不想使用适配功能, 请让该页面实现CancelAdapt接口放弃适配,所有的适配效果都将失效。


    7.框架核心

    1. 自定义适配
    通过字节跳动的核心源码,只能进行全局适配,但是该框架中进行了Activity和Fragmen的自定义适配和随时取消恢复适配功能。它的原理是注册了ActivityLifecycleCallbacks,进行了Activity的适配时间精准化自我掌控。

    通过注册ActivityLifecycleCallbacks,进行Activity的生命周期进行管理, 当onActivityCreated时,也就是OnCreate()的setContentView之前进行了AutoAdaptStrategy#applyAdapt的调用。这种方案类似于 AOP, 面向接口, 侵入性低, 方便统一管理, 扩展性强。

        @Override
        public void onActivityCreated(@androidx.annotation.NonNull Activity activity, Bundle savedInstanceState) {
            if (AutoSizeConfig.getInstance().isCustomFragment()) {
                if (mFragmentLifecycleCallbacksToAndroidx != null && activity instanceof androidx.fragment.app.FragmentActivity) {
                    ((androidx.fragment.app.FragmentActivity) activity).getSupportFragmentManager().registerFragmentLifecycleCallbacks(mFragmentLifecycleCallbacksToAndroidx, true);
                }
            }
            //Activity 中的 setContentView(View) 一定要在 super.onCreate(Bundle); 之后执行
            if (mAutoAdaptStrategy != null) {
                mAutoAdaptStrategy.applyAdapt(activity, activity);
            }
        }
    

    通过注册registerFragmentLifecycleCallbacks,进行Fragment的生命周期管理,当onFragmentCreated时,也就是OnCreate()中进行了AutoAdaptStrategy#applyAdapt的调用

    @Override
    public void onFragmentCreated(@androidx.annotation.NonNull FragmentManager fm, @androidx.annotation.NonNull Fragment f, Bundle savedInstanceState) {
            if (mAutoAdaptStrategy != null) {
                mAutoAdaptStrategy.applyAdapt(f, f.getActivity());
            }
        }
    
    

    通过全局的进行Activity和Fragment的生命周期监控,在其布局创建之前调用 AutoAdaptStrategy#applyAdapt进行具体的适配操作,它的关键点是动态修改density、scaledDensity、densityDpi三个参数,造成每个Activity或Fragment加载布局时的density、scaledDensity、densityDpi等参数不一样,达到的适配效果则不一样。


    2. 适配策略的实现
    ActivityLifecycleCallbacks的使用能实时监测Activity和Fragment进行适配调用,但是实际操作的代码在策略方案AutoAdaptStrategy的实现子类中,框架中已有默认策略方案,当然自己也可以自定义修改创建。

    • 当target实现CancelAdapt后,将density、scaledDensity、densityDpi恢复到原始状态,不进行匹配
    • 当target实现CustomAdapt后,将density、scaledDensity、densityDpi根据target的配置进行计算后设置
    • 当target未进行任何处理时,将density、scaledDensity、densityDpi根据AndroidManifest.xml中的配置进行计算设置
        @Override
        public void applyAdapt(Object target, Activity activity) {
        	....
            //如果 target 实现 CancelAdapt 接口表示放弃适配, 所有的适配效果都将失效
            if (target instanceof CancelAdapt) {
                AutoSizeLog.w(String.format(Locale.ENGLISH, "%s canceled the adaptation!", target.getClass().getName()));
                AutoSize.cancelAdapt(activity);
                return;
            }
    
            //如果 target 实现 CustomAdapt 接口表示该 target 想自定义一些用于适配的参数, 从而改变最终的适配效果
            if (target instanceof CustomAdapt) {
                AutoSizeLog.d(String.format(Locale.ENGLISH, "%s implemented by %s!", target.getClass().getName(), CustomAdapt.class.getName()));
                AutoSize.autoConvertDensityOfCustomAdapt(activity, (CustomAdapt) target);
            } else {
                AutoSizeLog.d(String.format(Locale.ENGLISH, "%s used the global configuration.", target.getClass().getName()));
                AutoSize.autoConvertDensityOfGlobal(activity);
            }
            ...
        }
       
    

    8. 其它

    1. Fragment横竖屏切换布局问题
    由于某些原因, 屏幕旋转后 Fragment 的重建, 会导致框架对 Fragment 的自定义适配参数失去效果。所以如果您的 Fragment 允许屏幕旋转, 则请在 onCreateView 手动调用一次 AutoSize.autoConvertDensity(),如AutoSize.autoConvertDensity(getActivity(), 1080, true)。
    如果您的 Fragment 不允许屏幕旋转, 则可以将下面调用 AutoSize.autoConvertDensity() 的代码删除掉

    public class CustomFragment1 extends Fragment implements CustomAdapt {
    
        @Nullable
        @Override
        public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
            //由于某些原因, 屏幕旋转后 Fragment 的重建, 会导致框架对 Fragment 的自定义适配参数失去效果
            //所以如果您的 Fragment 允许屏幕旋转, 则请在 onCreateView 手动调用一次 AutoSize.autoConvertDensity()
            //如果您的 Fragment 不允许屏幕旋转, 则可以将下面调用 AutoSize.autoConvertDensity() 的代码删除掉
            AutoSize.autoConvertDensity(getActivity(), 1080, true);
            return createTextView(inflater, "Fragment-1\nView width = 360dp\nTotal width = 1080dp", 0xffff0000);
        }
    

    2. 主副单位的逐步替换
    框架中同时支持主单位和副单位,对于对于旧项目中已使用dp或px的项目,可以通过逐步在新页面中使用主单位副单位。通过不断的迭代替换,最终将项目中的主单位如dp全替换为副单位px,或者将副单位px全替换为主单位dp。

    当单位都替换完成后,设置UnitsManager.setSupportDP(false)关闭对dp的支持,彻底隔离修改 density 所造成的不良影响。
    或者都使用dp,不在支持副单位时设置UnitsManager.setSupportSubunits(Subunits.NONE)关闭对副单位的支持。

    3. 主副单位的同时支持
    当使用者想将旧项目从主单位过渡到副单位, 或从副单位过渡到主单位时。因为在使用主单位时, 建议在 AndroidManifest 中填写设计图的 dp 尺寸, 比如 360 * 640。

    但在 AndroidManifest 中却只能填写一套设计图尺寸, 并且已经填写了主单位的设计图尺寸,所以当项目中同时存在副单位和主单位, 并且副单位的设计图尺寸与主单位的设计图尺寸不同时, 可以通过UnitsManager#setDesignSize() 方法配置。

    如果副单位的设计图尺寸与主单位的设计图尺寸相同, 则不需要调用 UnitsManager#setDesignSize(), 框架会自动使用 AndroidManifest 中填写的设计图尺寸。

    4. 自定义单位模拟器创建
    布局时的实时预览在开发阶段是一个很重要的环节, 很多情况下 Android Studio 提供的默认预览设备并不能完全展示我们的设计图。所以我们就需要自己创建模拟设备, 大神@JessYan已经为我们准备好了dp、pt、in、mm 这四种单位的模拟设备创建方法,请点击查看链接https://github.com/JessYanCoding/AndroidAutoSize/blob/master/README-zh.md#preview


    总结

    经过我自己修改注释的源码在https://github.com/l424533553/MyAutoSize中,大家也可以自行封装框架,适合自己的才是最好的。

    屏幕自适应的核心就是根据需要在使用之前不断修改density、scaledDensity、densityDpi达到适配效果。


    相关链接

    1. Android 今日头条屏幕适配详细使用攻略
    2. Lottie动画 轻松使用

    扩展链接:

    1. Android CameraX 使用入门
    2. Android studio 最全必用插件
    3. Android 史上最新最全的ADB及命令百科,没有之一

    博客书写不易,您的点赞收藏是我前进的动力,千万别忘记点赞、 收藏 ^ _ ^ !

    展开全文
  • 程序要做到用户配置的灵活性,就需要添加配置管理功能,这里使用.NET的应用程序配置文件app.config来保存配置信息,.NET Framework提供了对配置文件读写的良好支持。要实现配置文件的读取功能,需要引用System....

    本实例全部文章目录

    添加设置窗口

    在解决方案资源管理器窗口中,右键单击项目名称,在弹出的菜单中选择:添加》Windows窗体:


    输入窗体名称“frmSetup”:


    设置窗体的Text属性为“设置”,设置窗体的Size为“472, 276”,StartPosition属性为“CenterScreen”。

    添加设置标签页:

    左侧工具箱》窗器:双击“TabControl”


    设置的Dock属性为“Top”,Size属性为“456, 200”;

    添加标签页:


    添加三个标签页,Text分别设置为“基本设置,自动上传,自动保存”


    添加确定和取消按钮;

    基本设置标签页:


    从工具箱中添加两个GroupBox,分别为“热键、截图选项”;

    添加两个RadioButton,用于热键选择;

    添加四个CheckBox用于截图选项;

    添加两个TextBox用于设置放大镜的尺寸;

    添加两个PictureBox用于显示X和锁的图片;

    添加图片资源:

    双击Properties中的“Resources.resx”


    切换到图像视图:


    将图片复制,粘贴到这里,分别命名为“Lock,X”;

    设置PictureBox的Image属性为对应的资源:


    自动上传标签页:


    自动保存标签页:


    其中文件名称需要使用两个ComboBox,Items集合分别设置为:



    编写代码:

    双击设置窗体,切换到代码视图,添加私有变量:

            /// <summary>
            /// 保存Form1的句柄
            /// </summary>
            private IntPtr frm1Handle = IntPtr.Zero;

    修改构造函数:

            /// <summary>
            /// 构造函数
            /// </summary>
            /// <param name="frm1_Handle"></param>
            public frmSetup(IntPtr frm1_Handle)
            {
                InitializeComponent();
                this.frm1Handle = frm1_Handle;
            }

    为托盘菜单中的设置添加事件处理

    打开主窗体Form1的设计视图,选中“contextMenuStrip1”


    修改设置菜单的Name为“tsmi_Set”,双击设置菜单,添加代码:

            /// <summary>
            /// 托盘菜单设置事件处理程序
            /// </summary>
            /// <param name="sender"></param>
            /// <param name="e"></param>
            private void tsmi_Set_Click(object sender, EventArgs e)
            {
                frmSetup frm = new frmSetup(this.Handle);
                frm.ShowDialog();
            }
    编译,调试一下,通过托盘图标的右键菜单》设置 可以打开刚刚添加的设置窗口了。

    添加项目引用:

    主窗体添加相关配置项变量:

            #region 基本设置参数
            /// <summary>
            /// 截图时是否显示截图信息栏
            /// </summary>
            public bool InfoBoxVisible = true;
            /// <summary>
            /// 截图时是否显示编辑工具栏
            /// </summary>
            public bool ToolBoxVisible = true;
            /// <summary>
            /// 截图中是否包含鼠标指针形状
            /// </summary>
            public bool IsCutCursor = true;
            /// <summary>
            /// 截图时是否显示放大镜
            /// </summary>
            public bool ZoomBoxVisible = true;
            /// <summary>
            /// 放大镜的尺寸——宽度
            /// </summary>
            public int ZoomBoxWidth = 120;
            /// <summary>
            /// 放大镜的尺寸——高度
            /// </summary>
            public int ZoomBoxHeight = 100;
            #endregion
    
            #region 图片上传参数
            public string PicDescFieldName = "pictitle";
            public string ImageFieldName = "upfile";
            public string PicDesc = "cutImage";
            public string UploadUrl = "http://";
            public bool DoUpload = false;
            #endregion
    
            #region 自动保存参数
            /// <summary>
            /// 是否自动保存到硬盘
            /// </summary>
            public bool AutoSaveToDisk = false;
            /// <summary>
            /// 自动保存目录
            /// </summary>
            public string AutoSaveDirectory = string.Empty;
            /// <summary>
            /// 是否启用日期格式“2013_02_22”的子目录
            /// </summary>
            public bool AutoSaveSubDir = false;
            /// <summary>
            /// 自动保存文件名前缀
            /// </summary>
            public string AutoSaveFileName1 = "屏幕截图";
            /// <summary>
            /// 自动文件名规则:日期时间,日期_序号,序号
            /// </summary>
            public string AutoSaveFileName2 = "日期时间";
            /// <summary>
            /// 自动保存文件格式:.png, .jpg, .jpeg, .gif, .bmp
            /// </summary>
            public string AutoSaveFileName3 = ".png";
            /// <summary>
            /// 自动保存文件名序号
            /// </summary>
            public int autoSaveFileIndex = 0;
            #endregion 自动保存参数

    添加“AppSettingKeys”类:

        /// <summary>
        /// 提供配置文件中AppSettings节中对应的Key名称
        /// </summary>
        public static class AppSettingKeys
        {
            //基本设置
            public static string HotKeyMode = "HotKeyMode";
            public static string InfoBoxVisible = "InfoBoxVisible";
            public static string ToolBoxVisible = "ToolBoxVisible";
            public static string ZoomBoxVisible = "ZoomBoxVisible";
            public static string ZoomBoxWidth = "ZoomBoxWidth";
            public static string ZoomBoxHeight = "ZoomBoxHeight";
            public static string IsCutCursor = "IsCutCursor";
            //图片上传
            public static string PicDescFieldName = "PicDescFieldName";
            public static string ImageFieldName = "ImageFieldName";
            public static string PicDesc = "PicDesc";
            public static string UploadUrl = "UploadUrl";
            public static string DoUpload = "DoUpload";
            //自动保存
            public static string AutoSaveToDisk = "AutoSaveToDisk";
            public static string AutoSaveSubDir = "AutoSaveSubDir";
            public static string AutoSaveDirectory = "AutoSaveDirectory";
            public static string AutoSaveFileName1 = "AutoSaveFileName1";
            public static string AutoSaveFileName2 = "AutoSaveFileName2";
            public static string AutoSaveFileName3 = "AutoSaveFileName3";
    
        }

    Program.cs文件添加枚举类型:

        /// <summary>
        /// 控制键的类型
        /// </summary>
        public enum KeyModifiers : uint
        {
            None = 0,
            Alt = 1,
            Control = 2,
            Shift = 4,
            Windows = 8
        }

    警告:由于xxx是引用封送类的字段,访问上面的成员可能导致运行时异常

    设置窗口完整代码:

    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Data;
    using System.Drawing;
    using System.Linq;
    using System.Text;
    using System.Windows.Forms;
    
    namespace Screenshot
    {
        public partial class frmSetup : Form
        {
            /// <summary>
            /// 保存Form1的句柄
            /// </summary>
            private IntPtr frm1Handle = IntPtr.Zero;
    
            /// <summary>
            /// 构造函数
            /// </summary>
            /// <param name="frm1_Handle"></param>
            public frmSetup(IntPtr frm1_Handle)
            {
                InitializeComponent();
                this.frm1Handle = frm1_Handle;
            }
    
            /// <summary>
            /// 确定按钮单击事件处理程序
            /// </summary>
            /// <param name="sender"></param>
            /// <param name="e"></param>
            private void button_ok_Click(object sender, EventArgs e)
            {
                if (checkBox_autoSave.Checked && textBox_saveDir.Text.Trim().Length == 0)
                {
                    MessageBox.Show("您选择了“自动保存屏幕截图到磁盘”\n但还没有设置存储目录!");
                    return;
                }
                if (checkBox_autoSave.Checked && textBox_saveDir.Text.Trim().Length > 0)
                {
                    if (!System.Text.RegularExpressions.Regex.IsMatch(textBox_saveDir.Text.Trim(), "^[a-zA-Z]:\\\\[^/:\\*\\?\"<>\\|]*$", System.Text.RegularExpressions.RegexOptions.IgnoreCase))
                    {
                        MessageBox.Show("您选择了“自动保存屏幕截图到磁盘”\n但设置的存储目录不是有效的目录!");
                        return;
                    }
                    if (!System.IO.Directory.Exists(textBox_saveDir.Text.Trim()))
                    {
                        MessageBox.Show("您选择了“自动保存屏幕截图到磁盘”\n但设置的存储目录不存在!");
                        return;
                    }
                }
                Form1 frm = (Form1)Form.FromHandle(frm1Handle);
                if (frm != null)
                {
                    //基本设置
                    if (radioButton1.Checked) // && frm.HotKeyMode != 0 无论是否改变都重新注册热键,解决有时热键失效的问题
                    {
                        Form1.UnregisterHotKey(frm1Handle, frm.hotKeyId);
                        Form1.RegisterHotKey(frm1Handle, frm.hotKeyId, (uint)KeyModifiers.Control | (uint)KeyModifiers.Alt, Keys.A);
                        frm.HotKeyMode = 0;
                    }
    
                    if (radioButton2.Checked) // && frm.HotKeyMode != 1 无论是否改变都重新注册热键,解决有时热键失效的问题
                    {
                        Form1.UnregisterHotKey(frm1Handle, frm.hotKeyId);
                        Form1.RegisterHotKey(frm1Handle, frm.hotKeyId, (uint)KeyModifiers.Control | (uint)KeyModifiers.Shift, Keys.A);
                        frm.HotKeyMode = 1;
                    }
    
                    frm.InfoBoxVisible = ckb_InfoBox.Checked;
                    frm.ToolBoxVisible = ckb_ToolBox.Checked;
                    frm.IsCutCursor = ckb_CutCursor.Checked;
                    frm.ZoomBoxVisible = ckb_ZoomBox.Checked;
    
    
                    frm.ZoomBoxWidth1 = Convert.ToInt32(tb_zoomBoxWidth.Text);
                    frm.ZoomBoxHeight1 = Convert.ToInt32(tb_zoomBoxHeight.Text);
    
                    if (frm.ZoomBoxWidth1 < 120)
                    {
                        frm.ZoomBoxWidth1 = 120;
                        tb_zoomBoxWidth.Text = frm.ZoomBoxWidth1.ToString();
                    }
                    if (frm.ZoomBoxHeight1 < 100)
                    {
                        frm.ZoomBoxHeight1 = 100;
                        tb_zoomBoxHeight.Text = frm.ZoomBoxHeight1.ToString();
                    }
    
                    //图片上传
                    frm.PicDescFieldName = textBox_fieldDesc.Text;
                    frm.ImageFieldName = textBox_fieldFile.Text;
                    frm.PicDesc = textBox_desc.Text;
                    frm.UploadUrl = textBox_uploadUrl.Text;
                    frm.DoUpload = checkBox_upload.Checked;
    
                    //自动保存
                    frm.AutoSaveToDisk = checkBox_autoSave.Checked;
                    frm.AutoSaveSubDir = chb_subDir.Checked;
                    frm.AutoSaveDirectory = textBox_saveDir.Text;
    
                    frm.AutoSaveFileName1 = textBox_fileName1.Text;
                    if (comboBox_fileName2.SelectedItem != null)
                    {
                        frm.AutoSaveFileName2 = comboBox_fileName2.Text;
                    }
                    else
                    {
                        frm.AutoSaveFileName2 = "日期时间";
                    }
                    if (comboBox_Extn.SelectedItem != null)
                    {
                        frm.AutoSaveFileName3 = comboBox_Extn.Text;
                    }
                    else
                    {
                        frm.AutoSaveFileName3 = ".png";
                    }
                }
    
                SaveConfiguration();
    
                this.Close();
            }
    
            /// <summary>
            /// 保存配置信息到配置文件
            /// </summary>
            private void SaveConfiguration()
            {
                System.Configuration.Configuration config = System.Configuration.ConfigurationManager.OpenExeConfiguration(null);
    
                //基本设置
                SetConfigAppSetting(ref config, AppSettingKeys.HotKeyMode, radioButton1.Checked ? "1" : "0");
                SetConfigAppSetting(ref config, AppSettingKeys.InfoBoxVisible, ckb_InfoBox.Checked ? "1" : "0");
                SetConfigAppSetting(ref config, AppSettingKeys.ToolBoxVisible, ckb_ToolBox.Checked ? "1" : "0");
                SetConfigAppSetting(ref config, AppSettingKeys.IsCutCursor, ckb_CutCursor.Checked ? "1" : "0");
                SetConfigAppSetting(ref config, AppSettingKeys.ZoomBoxVisible, ckb_ZoomBox.Checked ? "1" : "0");
                SetConfigAppSetting(ref config, AppSettingKeys.ZoomBoxWidth, tb_zoomBoxWidth.Text);
                SetConfigAppSetting(ref config, AppSettingKeys.ZoomBoxHeight, tb_zoomBoxHeight.Text);
    
                //图片上传
                SetConfigAppSetting(ref config, AppSettingKeys.PicDescFieldName, textBox_fieldDesc.Text.Trim());
                SetConfigAppSetting(ref config, AppSettingKeys.ImageFieldName, textBox_fieldFile.Text.Trim());
                SetConfigAppSetting(ref config, AppSettingKeys.PicDesc, textBox_desc.Text.Trim());
                SetConfigAppSetting(ref config, AppSettingKeys.UploadUrl, textBox_uploadUrl.Text.Trim());
                SetConfigAppSetting(ref config, AppSettingKeys.DoUpload, checkBox_upload.Checked ? "1" : "0");
    
                //自动保存
                SetConfigAppSetting(ref config, AppSettingKeys.AutoSaveToDisk, checkBox_autoSave.Checked ? "1" : "0");
                SetConfigAppSetting(ref config, AppSettingKeys.AutoSaveSubDir, chb_subDir.Checked ? "1" : "0");
                SetConfigAppSetting(ref config, AppSettingKeys.AutoSaveDirectory, textBox_saveDir.Text.Trim());
                SetConfigAppSetting(ref config, AppSettingKeys.AutoSaveFileName1, textBox_fileName1.Text.Trim());
                if (comboBox_fileName2.SelectedItem != null)
                {
                    SetConfigAppSetting(ref config, AppSettingKeys.AutoSaveFileName2, comboBox_fileName2.Text);
                }
                else
                {
                    SetConfigAppSetting(ref config, AppSettingKeys.AutoSaveFileName2, "日期时间");
                }
                if (comboBox_Extn.SelectedItem != null)
                {
                    SetConfigAppSetting(ref config, AppSettingKeys.AutoSaveFileName3, comboBox_Extn.Text);
                }
                else
                {
                    SetConfigAppSetting(ref config, AppSettingKeys.AutoSaveFileName3, ".png");
                }
    
                config.Save(System.Configuration.ConfigurationSaveMode.Modified);
            }
    
            /// <summary>
            /// 设置配置信息
            /// </summary>
            /// <param name="config"></param>
            /// <param name="key"></param>
            /// <param name="value"></param>
            /// <returns></returns>
            private bool SetConfigAppSetting(ref System.Configuration.Configuration config, string key, string value)
            {
                try
                {
                    if (config.AppSettings.Settings[key] != null)
                    {
                        config.AppSettings.Settings[key].Value = value;
                    }
                    else
                    {
                        config.AppSettings.Settings.Add(key, value);
                    }
                    return true;
                }
                catch (Exception ex)
                {
                    MessageBox.Show(ex.Message + ex.Source + ex.StackTrace);
                    return false;
                }
            }
    
            /// <summary>
            /// 获取配置信息
            /// </summary>
            /// <param name="config"></param>
            /// <param name="key"></param>
            /// <returns></returns>
            private string GetConfigAppSetting(ref System.Configuration.Configuration config, string key)
            {
                try
                {
                    if (config.AppSettings.Settings[key] != null)
                    {
                        return config.AppSettings.Settings[key].Value;
                    }
    
                }
                catch (Exception ex)
                {
                    MessageBox.Show(ex.Message + ex.Source + ex.StackTrace);
                }
                return string.Empty;
            }
    
            /// <summary>
            /// 取消按钮单击事件处理程序
            /// </summary>
            /// <param name="sender"></param>
            /// <param name="e"></param>
            private void button_cancel_Click(object sender, EventArgs e)
            {
                this.Close();
            }
            /// <summary>
            /// 窗口加载事件处理程序
            /// </summary>
            /// <param name="sender"></param>
            /// <param name="e"></param>
            private void frmSetup_Load(object sender, EventArgs e)
            {
                chb_subDir.Text = "启用(按日期命名,格式:" + DateTime.Now.Date.ToString("yyyy_MM_dd") + ")";
    
                Form1 frm = (Form1)Form.FromHandle(frm1Handle);
                if (frm != null)
                {
                    //基本设置
                    if (frm.HotKeyMode == 0)
                    {
                        radioButton1.Checked = true;
                        radioButton2.Checked = false;
                    }
                    else
                    {
                        radioButton1.Checked = false;
                        radioButton2.Checked = true;
                    }
    
                    ckb_InfoBox.Checked = frm.InfoBoxVisible;
                    ckb_ToolBox.Checked = frm.ToolBoxVisible;
                    ckb_CutCursor.Checked = frm.IsCutCursor;
                    ckb_ZoomBox.Checked = frm.ZoomBoxVisible;
    
                    //图片上传
                    textBox_fieldDesc.Text = frm.PicDescFieldName;
                    textBox_fieldFile.Text = frm.ImageFieldName;
                    textBox_desc.Text = frm.PicDesc;
                    textBox_uploadUrl.Text = frm.UploadUrl;
                    checkBox_upload.Checked = frm.DoUpload;
    
                    //自动保存
                    checkBox_autoSave.Checked = frm.AutoSaveToDisk;
                    chb_subDir.Checked = frm.AutoSaveSubDir;
                    textBox_saveDir.Text = frm.AutoSaveDirectory;
                    textBox_fileName1.Text = frm.AutoSaveFileName1;
                    comboBox_fileName2.SelectedItem = frm.AutoSaveFileName2;
                    comboBox_Extn.SelectedItem = frm.AutoSaveFileName3;
    
    
                }
            }
            /// <summary>
            /// 浏览按钮事件处理程序
            /// </summary>
            /// <param name="sender"></param>
            /// <param name="e"></param>
            private void button_browse_Click(object sender, EventArgs e)
            {
                FolderBrowserDialog fbd = new FolderBrowserDialog();
                fbd.Description = "请选择屏幕截图的保存目录:";
                fbd.ShowNewFolderButton = true;
                fbd.RootFolder = Environment.SpecialFolder.MyComputer;
                fbd.SelectedPath = textBox_saveDir.Text;
                if (fbd.ShowDialog() == System.Windows.Forms.DialogResult.OK)
                {
                    textBox_saveDir.Text = fbd.SelectedPath;
                }
            }
            /// <summary>
            /// 更新自动保存文件名称示例
            /// </summary>
            private void UpdateFileNameExmple()
            {
                string AutoSaveFileName2 = string.Empty;
                if (comboBox_fileName2.SelectedItem != null)
                {
                    AutoSaveFileName2 = comboBox_fileName2.Text;
                }
                string AutoSaveFileName3 = ".png";
                if (comboBox_Extn.SelectedItem != null)
                {
                    AutoSaveFileName3 = comboBox_Extn.Text;
                }
    
                switch (AutoSaveFileName2)
                {
                    case "日期_序号":
                        textBox_exmple.Text = textBox_fileName1.Text + DateTime.Now.ToString("yyyy-MM-dd_") + "0001" + AutoSaveFileName3;
                        break;
                    case "序号":
                        textBox_exmple.Text = textBox_fileName1.Text + "0001" + AutoSaveFileName3;
                        break;
                    default:
                        textBox_exmple.Text = textBox_fileName1.Text + DateTime.Now.ToString("yyyy-MM-dd_HHmmss") + AutoSaveFileName3;
                        break;
                }
            }
    
            private void comboBox_fileName2_SelectedIndexChanged(object sender, EventArgs e)
            {
                UpdateFileNameExmple();
            }
    
            private void comboBox_Extn_SelectedIndexChanged(object sender, EventArgs e)
            {
                UpdateFileNameExmple();
            }
    
            private void textBox_fileName1_TextChanged(object sender, EventArgs e)
            {
                UpdateFileNameExmple();
            }
    
            // Boolean flag used to determine when a character other than a number is entered.
            private bool nonNumberEntered = false;
    
            private void tb_zoomBoxWidth_KeyDown(object sender, KeyEventArgs e)
            {
                // Initialize the flag to false.
                nonNumberEntered = false;
    
                // Determine whether the keystroke is a number from the top of the keyboard.
                if (e.KeyCode < Keys.D0 || e.KeyCode > Keys.D9)
                {
                    // Determine whether the keystroke is a number from the keypad.
                    if (e.KeyCode < Keys.NumPad0 || e.KeyCode > Keys.NumPad9)
                    {
                        // Determine whether the keystroke is a backspace.
                        if (e.KeyCode != Keys.Back)
                        {
                            // A non-numerical keystroke was pressed.
                            // Set the flag to true and evaluate in KeyPress event.
                            nonNumberEntered = true;
                        }
                    }
                }
                //If shift key was pressed, it's not a number.
                if (Control.ModifierKeys == Keys.Shift)
                {
                    nonNumberEntered = true;
                }
            }
    
            private void tb_zoomBoxHeight_KeyDown(object sender, KeyEventArgs e)
            {
                // Initialize the flag to false.
                nonNumberEntered = false;
    
                // Determine whether the keystroke is a number from the top of the keyboard.
                if (e.KeyCode < Keys.D0 || e.KeyCode > Keys.D9)
                {
                    // Determine whether the keystroke is a number from the keypad.
                    if (e.KeyCode < Keys.NumPad0 || e.KeyCode > Keys.NumPad9)
                    {
                        // Determine whether the keystroke is a backspace.
                        if (e.KeyCode != Keys.Back)
                        {
                            // A non-numerical keystroke was pressed.
                            // Set the flag to true and evaluate in KeyPress event.
                            nonNumberEntered = true;
                        }
                    }
                }
                //If shift key was pressed, it's not a number.
                if (Control.ModifierKeys == Keys.Shift)
                {
                    nonNumberEntered = true;
                }
            }
    
            private void tb_zoomBoxWidth_KeyPress(object sender, KeyPressEventArgs e)
            {
    
                // Check for the flag being set in the KeyDown event.
                if (nonNumberEntered == true)
                {
                    // Stop the character from being entered into the control since it is non-numerical.
                    e.Handled = true;
                }
            }
    
    
            private void tb_zoomBoxHeight_KeyPress(object sender, KeyPressEventArgs e)
            {
                // Check for the flag being set in the KeyDown event.
                if (nonNumberEntered == true)
                {
                    // Stop the character from being entered into the control since it is non-numerical.
                    e.Handled = true;
                }
            }
    
            /// <summary>
            /// 放大镜宽度改变事件处理
            /// </summary>
            /// <param name="sender"></param>
            /// <param name="e"></param>
            private void tb_zoomBoxWidth_TextChanged(object sender, EventArgs e)
            {
                int zoomWidth = Convert.ToInt32(tb_zoomBoxWidth.Text);
                if (zoomWidth < 120) { zoomWidth = 120; }
    
                tb_zoomBoxHeight.Text = ((int)(zoomWidth * 100 / 120)).ToString();
            }
    
            /// <summary>
            /// 放大镜高度改变事件处理
            /// </summary>
            /// <param name="sender"></param>
            /// <param name="e"></param>
            private void tb_zoomBoxHeight_TextChanged(object sender, EventArgs e)
            {
                int zoomHeight = Convert.ToInt32(tb_zoomBoxHeight.Text);
                if (zoomHeight < 100) { zoomHeight = 100; }
    
                tb_zoomBoxWidth.Text = ((int)(zoomHeight * 120 / 100)).ToString();
            }
        }
    }
    

    源码下载:http://download.csdn.net/detail/testcs_dn/7263143

    下一篇:C#软件开发实例.私人订制自己的屏幕截图工具(七)添加放大镜的功能

    展开全文
  • 因为平常使用外接大屏幕比较多,就选择了将外接大屏幕【2】设置为主显示器,如下所示: 这样就方便操作,在主显示屏幕上打开各种软件,很方便,不过呢,时间长了,发现一个问题,我搜索输入法,在输入汉字的...

    华为matebook2014,外接了一个hdmi显示器,这样除了原来笔记本电脑自带的屏幕外,多了一个屏幕,在“显示设置”里面,可以任意选择一个作为主显示器。因为平常使用外接大屏幕比较多,就选择了将外接大屏幕【2】设置为主显示器,如下所示:

     

    这样就方便操作,在主显示屏幕上打开各种软件,很方便,不过呢,时间长了,发现一个问题,我搜索输入法,在输入汉字的时候,拼音提示字在那个笔记本自带的小屏幕上面,而不是我打字的大屏幕光标右下角,这个就体验比较不友好了,因为拼音有时候你还需要去选择是哪一个汉字组合,在网上找了各种方案,也没有比较合适的方案。

     

    咨询了同事,他们的笔记本电脑也用了双屏幕,但是么有我的这个问题,他们使用的不是搜狗输入法而是系统自带的输入法,于是我就准备换输入法,换的时候,发现不好换,我之前安装搜索输入法的时候,卸载了系统自带的微软拼音输入法。重新下载安装微软拼音输入法之后,也没有生效。

     

    过了1天,我在体验其它功能的时候,尝试将笔主显示器换成了笔记本自带的【1】号显示器,结果打字发现拼音显示的汉字选择框已经自动纠正显示在光标的右下角了,如下所示:

     

    折磨了好几天的糟糕的输入法体验问题,就这样不知不觉的解决了,哈哈,真是神奇的体验啊。酸爽啊。

    展开全文
  • 因为上一篇文章提到的问题,小程序目前(2019年4月29日)暂时无法实现在打开下拉刷新功能的前提下,自己使用position:fixed制作底部tabbar标签导航栏,在下拉刷新时,页面上所有的fixed元素也都会失去fixed效果,会...
  • RedHat7系统每次不操作5-10分钟,就会自动锁屏,要重新出入密码,比较麻烦。 取消屏保步骤:应用程序----系统工具----设置----power----空白屏幕选择“从不” ...
  • 声明:本文旨在技术分享...相信大部分人在使用网页的时候,遇到过一些输入框用selenium的send_keys会无效吧~ 于是我采用了Pykeyboard提供的办法来实现输入,代码如下: keyboard = PyKeyboard() def send_key(...
  • android--屏幕旋转方法总结

    千次阅读 2014-11-11 14:09:24
    android屏幕旋转方法总结
  • 我在MainActivity中设置了时间,,比如是13:12。。。然后我锁屏。。最终到了13:12的时候点亮屏幕,显示我AlarmReceiver extends BroadcastReceiver中的dialog。。。该怎么做
  • 远程桌面按键失效变成快捷键

    千次阅读 2018-10-26 10:38:52
    一直在使用远程桌面连接Windows 2008操作系统,发现一个很烦的问题,经常发现某些时间,输入的按键变成了快捷键。 如弹出“辅助功能选项”、某些程序被快捷启动、按e出现资源管理器等。 又如按L键就直接回到登录...
  • windows屏幕亮度调节失灵的解决方法

    千次阅读 2021-05-02 00:00:55
    最近本人在工作中使用的笔记本电脑前段时间刚装好系统,右下角的屏幕亮度调节功能键的使用或者是亮度调节快捷键的使用都还是正常的。但不经意间重启一次电脑后前面说的那几个调节屏幕亮度的方法都失效了。 问题...
  • 如果本文对你有用,请爱心点个赞,提高排名,帮助更多的人。谢谢大家!❤ 如果解决不了,可以在文末进群交流。 ...阿里播放器接口丰富,功能强大,并且官方提供了...而且还提供了源码,如果功能不够项目扩展,可...
  • DX是一款运行于Microsoft Windows平台下的抓屏软件,利用它我们可以很方便地将屏幕上的任何一个部分,包括活动客户区域、 活动窗口、客户区域、桌面等抓取下来,供我们使用,确实是一个功能强大的抓图软件。...
  • 现在的智能手机都采用大屏幕、触控设计,一旦屏幕失灵,手机形同“板砖”,什么事情也做不了。那为什么手机会出现屏幕失灵,今天迅维快修小编就跟大家聊聊手机触控失灵的原因及应对办法。 手机出现屏幕失灵主要可以...
  • 联想小新系列笔记本重装系统后有可能会造成Fn+Q组合键失效的问题,导致无法改变笔记本的性能模式。 根本原因是格式化之后系统功能被卸载,而MSDN镜像中不含厂商附加功能,所以如果有重装意图的朋友可以参照...
  • 首先来说一下我的实现思路:用户选择好闹钟提醒时间后我要启动一个Service,在这个Service里面设置闹钟,通过闹钟直接打开一个Activity来显示提醒信息。我想要创建一个不在通知栏中显示notification的前台Service,...
  • win10系统 dshow 调用失效修复案例

    千次阅读 2017-03-15 17:57:18
    01 问题现象系统win10 企业版,安装了n多开发工具vs20XX,eclipseXX,adboe、VirtualBox、VMware、androidstudio等等。...但是有两个使用dshow做的开发程序无法录屏幕了。 https://github.com/5455945/VideoCap
  • wn32拦截ExtTextOut屏幕取词

    千次阅读 2014-04-27 12:39:28
    通过拦截ExtTextOut这个API可以实现屏幕取词的部分功能
  • 从Android6.0开始,系统提供了两种省电功能(延长电池寿命和使用时间):Doze和App Standby Doze和App Standby模式会延缓CPU和网络活动实现节能; 1.Doze模式 1.进入Doze模式 在手机未充电,完全静止且熄屏一段时间...
  • 如何在SYSTEM权限下实现屏幕监控

    千次阅读 2014-06-04 12:13:55
    屏幕监控是远控软件的基本功能之一。 版权声明 作者:iprowq 现在很多远控程序的服务端通常为DLL形式,通过远程线程注入等方法插入到services、svchost等SYSTEM权限的进程中去,而此时常规的屏幕监控就会失效(这...
  • 我也不觉得iOS系统比Android优秀,曾经有过一段时间将iPhone用作主流机。最后还是换成了安卓机,原因是iPhone的性价比的确不高,加上系统的一些限制,可玩性非常有限。而如果你问我,iOS系统里面有什么你特别喜欢的...
  • windows 快捷键关闭屏幕

    万次阅读 2019-02-27 16:06:48
    台式机没有Fn+F7关闭屏幕的快捷键,这里小bo找了一款相对好用的小工具,设置快捷方式/设置快捷键,并且“启动后即执行功能”,即可实现快捷键关闭屏幕功能了! 下载地址:...
  • 屏幕截图是Mac提供的内置功能,很少有它不起作用。但是由于某些意外的设置或硬件问题,Mac上的屏幕截图有时无法正常工作,这里提供的是Mac上的屏幕截图不起作用该如何修复? 1.在Mac上启用屏幕快照快捷方式 如果您...
  • iOS使其支持侧滑功能

    千次阅读 2016-08-29 14:15:38
    方案一:开启使用系统自带的侧滑返回iOS7之后系统提供了侧滑手势(interactivePopGestureRecognizer),即从屏幕左侧边缘滑起会pop回导航控制器栈的上个viewController。不过如果你自定义了...
  • 在计算机产业发展的70年时间里,每一次的 IT 革命,无不带来:更低廉的价格、更完善的功能、更便捷的使用、更广阔的市场! 大数据经过10年发展,现在已经到了一个重要的分水岭阶段:通用性和兼容性能力成为大数据...
  • ANSI的Esc屏幕控制码 在终端中,ANSI定义了用于屏幕显示的Escape屏幕控制码,在printf函数调用时可以输出具有颜色的字符,其格式如下:"\033[字背景颜色;字体颜色m字符串\033[0m" 下面是常见的一些ANSI控制码。加...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 30,777
精华内容 12,310
关键字:

屏幕使用时间功能失效