安卓开发之自定义下拉刷新头部

 更新时间:2016年9月20日 19:53  点击:2323
下拉刷新在手机中我们常了,几乎所有的app都具备了一个下拉刷新页面的功能了,那么我们一起来看安卓中下拉刷新实现方法。

一直用的下拉刷新库就是android-Ultra-Pull-to-Refresh,本身这个库就带有几种样式的下拉刷新头部,大家可以去git看一下,地址https://github.com/liaohuqiu/android-Ultra-Pull-To-Refresh。最为方便的是我们可以自己定制各式各样的头部。最近项目有个自定义的下拉头部,自己研究了一下。实现效果是:

无标题

我们要做的就是自己写一个样式xml文件,然后实现PtrUIHandler这个接口,代码如下:


public class LoadMoreFooterView extends FrameLayout implements PtrUIHandler {
    private LayoutInflater inflater;
 
    // 下拉刷新视图(头部视图)
    private ViewGroup headView;
 
    // 下拉刷新文字
    private TextView tvHeadTitle;
 
    // 下拉图标
    private ImageView ivWindmill;
 
  //  private WindmillDrawable drawable;
 
    public LoadMoreFooterView(Context context) {
        this(context, null);
    }
 
    public LoadMoreFooterView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }
 
    public LoadMoreFooterView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context);
    }
 
    /**
     * 初始化
     *
     * @param context
     */
    private void init(Context context) {
 
        inflater = LayoutInflater.from(context);
        /**
         * 头部
         */
        headView = (ViewGroup) inflater.inflate(R.layout.widget_header, this, true);
        ivWindmill = (ImageView) headView.findViewById(R.id.iv_windmill);
        tvHeadTitle = (TextView) headView.findViewById(R.id.tv_head_title);
        ivWindmill.setVisibility(VISIBLE);
        ivWindmill.setImageResource(R.mipmap.icon_logo);
        tvHeadTitle.setText("下拉刷新");
 
    }
 
    @Override
    public void onUIReset(PtrFrameLayout ptrFrameLayout) {
        tvHeadTitle.setText("下拉刷新");
 
    }
 
    @Override
    public void onUIRefreshPrepare(PtrFrameLayout ptrFrameLayout) {
        tvHeadTitle.setText("下拉刷新");
    }
 
    @Override
    public void onUIRefreshBegin(PtrFrameLayout ptrFrameLayout) {
        tvHeadTitle.setText("正在刷新");
 
    }
 
    @Override
    public void onUIRefreshComplete(PtrFrameLayout ptrFrameLayout) {
        ivWindmill.clearAnimation();
        tvHeadTitle.setText("刷新完成");
    }
 
    @Override
    public void onUIPositionChange(PtrFrameLayout frame, boolean isUnderTouch, byte status, PtrIndicator ptrIndicator) {
        final int mOffsetToRefresh = frame.getOffsetToRefresh();
        final int currentPos = ptrIndicator.getCurrentPosY();
        final int lastPos = ptrIndicator.getLastPosY();
 
        if (currentPos < mOffsetToRefresh && lastPos >= mOffsetToRefresh) {
            if (isUnderTouch && status == PtrFrameLayout.PTR_STATUS_PREPARE) {
                tvHeadTitle.setText("下拉刷新");
            }
        } else if (currentPos > mOffsetToRefresh && lastPos <= mOffsetToRefresh) {
            if (isUnderTouch && status == PtrFrameLayout.PTR_STATUS_PREPARE) {
                tvHeadTitle.setText("松开刷新");
            }
        }
 
    }
 
}


使用:

 

/* 创建自定义刷新头部view */
LoadMoreFooterView header = new LoadMoreFooterView(this);
/* 设置刷新头部view */
ptr_view.setHeaderView(header);
/* 设置回调 */
ptr_view.addPtrUIHandler(header);
ptr_view.setPtrHandler(new PtrHandler() {
    @Override
    public boolean checkCanDoRefresh(PtrFrameLayout frame, View content, View header) {
        return PtrDefaultHandler.checkContentCanBePulledDown(frame, content, header);
    }
 
    @Override
    public void onRefreshBegin(PtrFrameLayout frame) {
        ptr_view.postDelayed(new Runnable() {
            @Override
            public void run() {
              
                getdata();
            }
        }, 2000);
    }
});
    /* 延时100秒,自动刷新 */
ptr_view.postDelayed(new Runnable() {
    @Override
    public void run() {
        ptr_view.autoRefresh();
    }
}, 100);

很多APP应用喜欢在首屏显示大广告效果,这样可以在启动APP还没连上网络时不会显示那么空白,还可以增加广告效果。本文我们来分享一下这样子的功能在Android开发中如何实现。

本文我们分享一个Android APP 首屏广告示例, 这里我们主要讲的是实现原理及一些页面的架构,代码只能作参考。

广告需求图:

广告需求图



1. 显示本地存储广告图片, 点击图片, 跳转广告链接, 并提供微信分享功能.
2. 异步下载广告信息, 提高启动速度; 异步下载并保存广告和分享图片, 提高加载速度.

开发过程中, 使用了一些小技巧, 我会详细讲解注意的要点, 包括:
(1) 使用RxAndroid库, 在新线程上做异步下载广告信息.
(2) 使用Picasso库, 异步下载图片(Bitmap)并存储至本地.
(3) 使用原生Handler类, 实现计时器功能, 按秒跳转数字.
(4) 使用WebView视图, 加载广告链接, 并提供分享功能.

1. 下载广告

在欢迎页面中, 启动一个异步线程, 加载广告信息, 提高启动速度, 防止网速过慢导致切换卡顿.

// 异步广告信息
private void AsyncCheckInfo() {
    // 异步线程处理监听, 在新线程上监听, 发送到主线程
    Observableobservable = Observable.create(new Observable.OnSubscribe() {
        @Override
        public void call(Subscriber subscriber) {
            subscriber.onNext(checkInfo());
            subscriber.onCompleted();
        }
    }).subscribeOn(Schedulers.newThread()).observeOn(AndroidSchedulers.mainThread());

    // 成功回调
    observable.subscribe(new Subscriber() {
        @Override
        public void onCompleted() {
            Log.i(TAG, "onCompleted");
        }

        @Override
        public void onError(Throwable e) {
        }

        @Override
        public void onNext(String s) {
            Log.i(TAG, "onNext");
        }
    });
}

在新线程(newThread)中加载, 完成后发送到主线程(mainThread). 参考.

判断网络, 在有网的时候, 加载广告信息; 在无网的时候, 直接略过.

// 加载广告信息
public String checkInfo() {
    if (NetUtils.isNetworkConnected(ChunyuApp.getAppContext())) {
        UpdateUtils.checkDailyInfo(WelcomeActivity.this, mDailyRequestCallback);
        return "Begin to load info.";
    } else {
        return "Stop to load info";
    }
}

在UpdateUtils.checkDailyInfo中, 解析广告请求的返回值. 如果包含广告信息, 则存储在首选项(SharedPreference)中, 下次启动广告直接读取; 如果不包含广告信息, 则设置无数据标记, 在使用时判定无广告.
最后调用回调接口mDailyRequestCallback继续处理.

ArrayListadverts = version.advert;
if (adverts.size() > 0) {
    for (int i = 0; i < adverts.size(); ++i) {
        Advert advert = adverts.get(i);
        if (advert.Number == 1) { // Number等于0是广告
            PedometerAdManager.getInstance().init(advert);
        }
    }
} else {
    Log.e(TAG, "广告是空");
    SharedPreferences sp =
            PreferenceManager.getDefaultSharedPreferences(ChunyuApp.getAppContext());
    sp.edit().putBoolean(WelcomeActivity.FIRST_AD_IS_HAVE_PREFS, false).apply();
}


2. 存储图片

已经存储广告信息之后, 即可获得图片下载链接, 为了提高显示速度, 下载图片存储在本地. 因为下载属于网络请求, 需要异步处理, 本文使用Picasso库, 没有发明轮子.

// 日常信息回调
private final UpdateUtils.DailyRequestCallback mDailyRequestCallback
        = new UpdateUtils.DailyRequestCallback() {
    @Override
    public void operationExecutedSuccess() {
        if (mAdManager.getImageUrl() != null && !mAdManager.getImageUrl().isEmpty())
            Picasso.with(WelcomeActivity.this).
                    load(mAdManager.getImageUrl()).into(mAdImageTarget);

        if (mAdManager.getShareIcon() != null && !mAdManager.getShareIcon().isEmpty())
            Picasso.with(WelcomeActivity.this).
                    load(mAdManager.getShareIcon()).into(mAdShareImageTarget);
    }

    @Override
    public void operationExecutedFailed() {
        Log.e(TAG, "operationExecutedFailed");
    }
};

// 广告图片
private Target mAdImageTarget = new Target() {
    @Override
    public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) {
        String path = FileUtility.savePic(bitmap);
        mPrefs.edit().putString(FIRST_AD_PATH_PREFS, path).apply();
    }

    @Override public void onBitmapFailed(Drawable errorDrawable) {

    }

    @Override public void onPrepareLoad(Drawable placeHolderDrawable) {

    }
};

// 分享Icon
private Target mAdShareImageTarget = new Target() {
    @Override
    public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) {
        String path = FileUtility.savePic(bitmap);
        mPrefs.edit().putString(FIRST_AD_SHARE_IMAGE_URL_PREFS, path).apply();
    }

    @Override public void onBitmapFailed(Drawable errorDrawable) {
    }

    @Override public void onPrepareLoad(Drawable placeHolderDrawable) {

    }
};

在页面暂停时, 移除Picasso的请求线程.

@Override
protected void onPause() {
    MobclickAgent.onPause(this);
    handler.removeCallbacks(runnable); // 停止
    Picasso.with(this).cancelRequest(mAdImageTarget); // 停止
    Picasso.with(this).cancelRequest(mAdShareImageTarget); // 停止
    super.onPause();
}

注意: 在Picasso中, Target是和ImageView控件弱绑定, 在销毁ImageView时, 会随之销毁. 如果未提供ImageView控件, 需要手动销毁请求, 如在onPause中取消. 否则会出现下载异常. 参考.

3. 显示广告

首先Logo页显示LOGO_TIME秒, 再判断显示引导(首次启动)或显示广告.
显示广告是使用存储在首选项(SharedPreference)中的数据, 图片使用本地资源解析, 提高显示速度.

// 显示启动信息
private void showLaunchInfo() {
    // 显示一段时间的主屏Logo
    new Handler().postDelayed(this::showAdInfo, LOGO_TIME);
}

// 显示广告信息
private void showAdInfo() {
    // 判断是否有广告
    if (mPrefs.getBoolean(FIRST_AD_IS_HAVE_PREFS, false)) {
        Log.e(TAG, "包含广告");
        String path = mPrefs.getString(FIRST_AD_PATH_PREFS, "");
        if (!path.isEmpty()) {
            int time = mPrefs.getInt(FIRST_AD_TIME_PREFS, 0);
            Bitmap bitmap = BitmapFactory.decodeFile(path);
            Log.e(TAG, "time: " + time);
            showAdImage(bitmap, time);
            if (!NetUtils.isNetworkConnected(ChunyuApp.getAppContext())) {
                mIvWebImage.setClickable(false);
            }
        } else {
            gotoOtherActivity();
        }
    } else {
        gotoOtherActivity();
    }
}


显示的广告使用上次网络请求的存储数据, 也可能是本次网络请求的, 主要取决于在LOGO_TIME时间中, 是否下载完成启动信息, 并存储至本地.

4. 广告计时器

在广告图片显示时, 提供倒计时器, 按秒跳时, 提供跳过按钮直接跳过广告.

// 显示广告
private void showAdImage(Bitmap bitmap, int time) {
    mIvWebImage.setVisibility(View.VISIBLE);
    mTvSkip.setVisibility(View.VISIBLE);
    mTvSkip.setOnClickListener(v -> gotoOtherActivity());
    mIvBackground.setVisibility(View.INVISIBLE);
    mIvFirstLogo.setVisibility(View.INVISIBLE);

    mIvWebImage.setImageBitmap(bitmap);
    mAdTime = time + 2;
    handler.post(runnable); // 设置读秒
}

// 设置读秒器
private int s = 0; // 时间Delay
private final Handler handler = new Handler();
private final Runnable runnable = new Runnable() {
    @Override
    public void run() {
        // handler自带方法实现定时器
        try {
            handler.postDelayed(this, 1000);

            if (s < 1) {
                s++;
                return;
            }

            if (s <= (mAdTime - 1)) {
                mTvSkip.setText(String.valueOf("跳过\n"
                        + Integer.toString((mAdTime - 1) - (s++)) + "秒"));
            }

            // 计时器为0时, 开始跳转
            if (s == mAdTime) {
                gotoOtherActivity();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
};

广告时间额外显示两秒, 提供页面跳转间隔, 前一秒后一秒, 保证广告时间充足.

在广告页跳转或页面结束时, 删除计时回调.

// 跳转到现实广告的视图
public void gotoShowAdView(View view) {
    NV.o(this, AdvertisementActivity.class);
    handler.removeCallbacks(runnable);
    finish();
}

@Override
protected void onPause() {
    MobclickAgent.onPause(this);
    handler.removeCallbacks(runnable); // 停止
    Picasso.with(this).cancelRequest(mAdImageTarget); // 停止
    Picasso.with(this).cancelRequest(mAdShareImageTarget); // 停止
    super.onPause();
}

本文使用handler类, 循环调用计时, 必须在离开页面时, 清除runnable回调. 否则会遗忘线程泄露内存.

5. 链接页面

点击广告图片, 会跳转至广告链接, 根据参数设置全屏或者提供分享功能, 把链接分享至微信. 微信分享需要标题, 内容, 图标(Icon), 其中图片是从服务器下载后预存在本地.

/**
 * 广告Activity
 *


 * Created by wangchenlong on 15/12/2.
 */
public class AdvertisementActivity extends PActivity {

    @SuppressWarnings("unused")
    private static final String TAG = "DEBUG-WCL: "
            + AdvertisementActivity.class.getSimpleName();

    @Bind(R.id.advertise_pwv_container) PedoWebView mPwvContainer;
    @Bind(R.id.advertise_ll_back_home) LinearLayout mLlBackHome;
    @Bind(R.id.advertise_ll_send_session) LinearLayout mLlSendSession;
    @Bind(R.id.advertise_ll_send_timeline) LinearLayout mLlSendTimeline;
    @Bind(R.id.advertise_ll_action_bar) LinearLayout mLlActionBar;

    private SharedPreferences mPrefs;
    private int mFlag; // 判断分享地点

    private static final int WECHAT_SESSION = 0;    // 微信对话
    private static final int WECHAT_TIMELINE = 1;   // 朋友圈

    @Override protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_advertisement);
        ButterKnife.bind(this);

        mPrefs = PreferenceManager.getDefaultSharedPreferences(ChunyuApp.getAppContext());

        ActionBar actionBar = getSupportActionBar();
        if (actionBar != null)
            actionBar.setDisplayHomeAsUpEnabled(true);

        // 是否全屏
        if (mPrefs.getBoolean(WelcomeActivity.FIRST_AD_IS_FULL_PREFS, false)) {
            mLlActionBar.setVisibility(View.GONE);
        } else {
            mLlBackHome.setOnClickListener(v -> {
                NV.o(this, PedometerActivity.class);
                finish();
            });

            // 是否分享
            if (mPrefs.getBoolean(WelcomeActivity.FIRST_AD_IS_SHARE_PREFS, false)) {
                mLlSendSession.setOnClickListener(v -> {
                    mFlag = WECHAT_SESSION;
                    shareWechat();
                });
                mLlSendTimeline.setOnClickListener(v -> {
                    mFlag = WECHAT_TIMELINE;
                    shareWechat();
                });
            } else {
                mLlSendSession.setVisibility(View.GONE);
                mLlSendTimeline.setVisibility(View.GONE);
            }
        }

        mPwvContainer.loadUrl(mPrefs.getString(WelcomeActivity.FIRST_AD_URL_PREFS, ""));
    }

    // 分享到微信
    public void shareWechat() {
        IWXAPI wxapi =
                WXAPIFactory.createWXAPI(ChunyuApp.getAppContext(), SNSConst.WX_APP_ID_ONLINE, true);
        wxapi.registerApp(SNSConst.WX_APP_ID_ONLINE);

        WXWebpageObject webpage = new WXWebpageObject();
        webpage.webpageUrl = mPrefs.getString(WelcomeActivity.FIRST_AD_URL_PREFS, "");
        WXMediaMessage msg = new WXMediaMessage(webpage);
        msg.title = mPrefs.getString(WelcomeActivity.FIRST_AD_SHARE_TITLE_PREFS, "");
        msg.description = mPrefs.getString(WelcomeActivity.FIRST_AD_SHARE_CONTENT_PREFS, "");

        String path = mPrefs.getString(WelcomeActivity.FIRST_AD_SHARE_IMAGE_URL_PREFS, "");
        if (!path.isEmpty()) {
            Bitmap bitmap = BitmapFactory.decodeFile(path);
            if (bitmap != null) {
                msg.setThumbImage(bitmap);
            } else {
                msg.setThumbImage(BitmapFactory.decodeResource(getResources(), R.drawable.icon));
            }
            SendMessageToWX.Req req = new SendMessageToWX.Req();
            req.transaction = String.valueOf(System.currentTimeMillis());
            req.message = msg;
            req.scene = ((mFlag == 0) ?
                    SendMessageToWX.Req.WXSceneSession : SendMessageToWX.Req.WXSceneTimeline);
            wxapi.sendReq(req);
        }
    }

    @Override public void onBackPressed() {
        if (mPwvContainer.canGoBack()) {
            mPwvContainer.goBack();
        } else {
            NV.o(this, PedometerActivity.class);
            finish();
        }
    }
}

调用后退按钮(onBackPressed): 在网页跳转多页时, 返回上一页; 在首页时, 退出广告页面, 跳转主页. 微信分享的图标(Icon), 最好使用方形全图, 否则透明部分会被黑色替代, 服务器提供图片时需要注意.



做安卓开发的朋友我相信对于Navigationview肯定不陌生了,今天我们一起来看看Navigationview例子吧。

最近在研究侧滑菜单时发现了一些问题,如果你之前没有接触过肯定会去百度,而我也看了很多demo,相信大家看到的例子都是下面那样布局的

headerLayout加载头布局,menu加载菜单,这样就组成了一个完整的菜单,那么问题来了,menu的点击事件网上都贴出来了,很简单,那么头部呢?你可能会无从下手,那么只有看源码了,NavigationView 中有inflateHeaderView这个方法,看到这个方法你肯定就会觉得是通过这个方法加载头布局,好吧,现在方法有了,但是当你通过这个方法加载时会发现菜单中出现了两个头布局,很显然是加载了两次,第一次就是在布局文件中指定了headerLayout,当你滑动菜单时就会加载这个头布局,第二次是你在代码中又加载了一次。所以会出现两个布局。只要将布局中的headerLayout那行代码删除就可以实现你要的效果。下面给出绑定头部布局的代码


//布局文件
<android.support.design.widget.NacigationView
android:layout_width="wrap_content"
android:layout_height="match_parent"
andriod:layout_gravity="start"
app:headerLayout="@layout/drawer_header"
app:menu="@menu/drawer"/>
 
//绑定侧滑菜单headerlayout布局,
 
View drawview = nav_view.inflateHeaderView(R.layout.view_leftmenu);
ImageView user_pic = (ImageView) drawview.findViewById(R.id.imag_user_pic);
通过NavigationView 来加载头布局后再进行控件绑定就可以解决问题。

在Android开发中,View定义了绘图的基本操作。很多时候系统自带的View满足不了设计的要求,这时我们需要自定义View,本文就来学习一下Android 自定义控件(view)的简单例子。

Android自定义view通过继承系统的View并重写部分方法来满足自己的特定需要。首先我们来看一下都有哪些方法可能需要被重写:   

    onMeasure() 检测View组件及其子组件的大小
    onLayout() 当该组件需要分配其子组件的位置、大小时
    onTouchEvent 当发生触屏事件时
    onDraw() 当组件将要绘制它的内容时
    onKeyDown 当按下某个键盘时
    onKeyUp  当松开某个键盘时
    onTrackballEvent 当发生轨迹球事件时
    onSizeChange() 当该组件的大小被改变时
    onFinishInflate() 回调方法,当应用从XML加载该组件并用它构建界面之后调用的方法
    onWindowFocusChanged(boolean)  当该组件得到、失去焦点时
    onAtrrachedToWindow() 当把该组件放入到某个窗口时
    onDetachedFromWindow() 当把该组件从某个窗口上分离时触发的方法
    onWindowVisibilityChanged(int): 当包含该组件的窗口的可见性发生改变时触发的方法
 
红色标注的部分是我们经常需要重写的函数。具体的实现我们举一个简单的例子来说明,首先上效果图:

 
圆形和文字跟随触摸事件移动的一个简单的自定义view

实现上面的效果我们大致需要分成这几步
    在res/values/  下建立一个attrs.xml 来声明自定义view的属性
    一个继承View并复写部分函数的自定义view的类
    一个展示自定义view 的容器界面

我们的view 叫做myView,一定要和我们的class文件名相同。它有一个属性值,格式为color

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <declare-styleable name="myView">
        <attr name="TextColor" format="color"/>
    </declare-styleable>
        
</resources>

2.在自定义view类中实现其构造函数(用于初始获得view的属性配置)和复写onDraw和onTouchEvent。

public class myView extends View{
    //定义画笔和初始位置
    Paint p = new Paint();
    public float currentX = 50;
    public float currentY = 50;
    public int textColor;

    public myView(Context context, AttributeSet attrs) {
        super(context, attrs);
        //获取资源文件里面的属性,由于这里只有一个属性值,不用遍历数组,直接通过R文件拿出color值
        //把属性放在资源文件里,方便设置和复用
        TypedArray array = context.obtainStyledAttributes(attrs,R.styleable.myView);
        textColor = array.getColor(R.styleable.myView_TextColor,Color.BLACK);
        array.recycle();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //画一个蓝色的圆形
        p.setColor(Color.BLUE);
        canvas.drawCircle(currentX,currentY,30,p);
        //设置文字和颜色,这里的颜色是资源文件values里面的值
        p.setColor(textColor);
        canvas.drawText("BY finch",currentX-30,currentY+50,p);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {

        currentX = event.getX();
        currentY = event.getY();
        invalidate();//重新绘制图形
        return true;

    }
}

这里思路很简单,通过不断的更新当前位置坐标和重新绘制图形实现效果,要注意的是使用TypedArray后一定要记得recycle(),否则会对下次调用产生影响。

除非你不会再用TypedArray. 


3.我们把myView放在activity_main.xml里面,当然也可以在代码中通过addview函数加到布局中。

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:myview="http://schemas.android.com/apk/res-auto"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="finch.scu.cn.myview.MainActivity">

    <finch.scu.cn.myview.myView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        myview:TextColor="#ff0000"
        />
</RelativeLayout>

这里 xmlns:自定义控件的前缀="http://schemas.android.com/apk/res/包名(或res-auto)" , 前缀:TextColor="#ff0000"。如果不申明命名空间属性就会


最后是MainActivity

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}

具体的view要根据具体的需求来,比如我们要侧滑删除的listview我们可以继承listview,监听侧滑事件,显示删除按钮实现功能。




Android自定义View的实现

自定义View首先要实现一个继承自View的类。添加类的构造方法,override父类的方法,如onDraw,(onMeasure)等。如果自定义的View有自己的属性,需要在values下建立attrs.xml文件,在其中定义属性,同时代码也要做修改。

一个简单的例子:

·新建一个MyView类,继承自TextView,并添加构造方法:

package com.example.xhelloworld;

import android.content.Context;

import android.widget.TextView;

public class MyView extends TextView{

    public MyView(Context context) {

       super(context);

       // TODO Auto-generated constructor stub

    }

}

·再在主activity中调用。方法是setContentView(new MyView(this));这句

package com.example.xhelloworld;

import android.app.Activity;

import android.os.Bundle;

import android.view.Menu;

public class NewView extends Activity {

    @Override

    public void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        //setContentView(R.layout.activity_newview);

        setContentView(new MyView(this));

       

    }

    @Override

    public boolean onCreateOptionsMenu(Menu menu) {

        getMenuInflater().inflate(R.menu.activity_newview, menu);

        return true;

    }

}

运行后的结果为:


这样一个简单的自定义View就可以使用了。可以改变一下背景颜色,在MyView类中添加:

@Override

    protected void onDraw(Canvas canvas) {

       // TODO Auto-generated method stub

       super.onDraw(canvas);

       canvas.drawColor(Color.BLUE);

    }

即可完成。运行结果


上面的例子很简单,没有涉及到属性的添加。使用范围很小,不能在布局文件中使用。如果要在布局文件中用到,还需要添加一个构造方法:

public MyView(Context context,AttributeSet attrs){

       super(context, attrs);  

    }

当然,上面只是在code中做的修改,在xml文件(main.xml)中也需要进行如下操作:

<com.example.xhelloworld.NewView

        android:layout_width="wrap_content"

        android:layout_height="wrap_content"

        />

至少在xml文件中写上上面的内容。其中com.example.xhelloworld.NewView这句是需要显示的控件所代表的类。Com.example.xhelloworld是类的包名,NewView是类名。这个类肯定是继承自View的自定义类(其实就是,使我们自己写的,这是废话了。。。),可以是在工程中直接源码添加xxxx.java的,也可以是在libs目录下自己新添加的jar包里面的。如果是jar包里面的一个类,则路径就是jar包里面,这个类的路径。

完成上面的两步之后就可以在代码中实例化这个布局文件了

@Override

    public void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.main);

        //setContentView(new MyView(this));

显示的效果同上图。

下面介绍如何实现自定义View的属性设置。实现自定义View的属性设置,需要:

·在values目录下建立attrs.xml文件,添加属性内容

·在布局文件中添加新的命名空间xmlns,然后可以使用命名空间给自定义的空间设置属性

·设置完属性之后,当然还要对其进行处理。在自定义View类中的构造方法中进行处理

根据这三步给一个例子进行说明一下

首先添加attrs.xml文件,在定义属性

 

<resources>

    <declare-styleable name="MyView">

    <attr name="textColor" format="color"/>

    <attr name="textSize" format="dimension"/>

    declare-styleable>

resources>

然后在布局文件中完成:

xmlns:my=http://schemas.android.com/apk/res/com.example.xhelloworld

<com.example.xhelloworld.MyView

       android:layout_width="fill_parent"

       android:layout_height="wrap_content"   

       my:textColor="#FFFFFFFF"   

       my:textSize="22dp"

    />

注:这步我在实现的时候出错,问题是显示找不到属性textColor和textSize,这奇怪的错误。解决方法是,在写my:textColor="#FFFFFFFF" 时,写到my之后,按alt+/,这是会自动添加一个xmlns,和my的路径是一样的,用生成的这个替换掉my就可以了。奇葩的问题就用奇葩的方法解决。起初我也不知道怎么弄,瞎搞出来的。

最后在MyView.java中添加另一个构造方法,并添加代码来处理从xml中获得的属性

public MyView(Context context,AttributeSet attrs){

       super(context, attrs);

         mPaint = new Paint();  

        //TypedArray是一个用来存放由context.obtainStyledAttributes获得的属性的数组  

        //在使用完成后,一定要调用recycle方法  

        //属性的名称是styleable中的名称+“_”+属性名称  

        //TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.MyView);

        TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.MyView);

        int textColor = array.getColor(R.styleable.MyView_textColor, 0XFF00FF00); //提供默认值,放置未指定  

        float textSize = array.getDimension(R.styleable.MyView_textSize, 36);  

        mPaint.setColor(textColor);  

        mPaint.setTextSize(textSize);  

        array.recycle(); //一定要调用,否则这次的设定会对下次的使用造成影响  

      

    }

 

完成之后就已经实现了自定视图的构造,自定义视图属性的添加很处理。现在完成的是一般的自定义视图,继承自TextView或者View等视图,也就是通过程序主UI线程完成更新的视图,如果是自定义SurfaceView,实现方法有所不同。

添加完之后肯定有很多疑问,自己去做可能还不能理解。这里再对上面操作进行解释说明。

背后的事

View类的构造方法:

·public view(Context context)       当在代码中创建对象时会被调用

·public View (Context context, AttributeSet attrs)

官方的文档是:

Constructor that is called when inflating a view from XML. This is called when a view is being constructed from an XML file, supplying attributes that were specified in the XML file. This version uses a default style of 0, so the only attribute values applied are those in the Context's Theme and the given AttributeSet

大致应该是这个方法是通过xml文件来创建一个view对象的时候调用。很显然xml文件一般是布局文件,就是现实控件的时候调用,而布局文件中免不了会有属性的设置,如android:layout_width等,对这些属性的设置对应的处理代码也在这个方法中完成。

两个参数

Context          The Context the view is running in, through which it can access the current theme, resources, etc.

Attrs              The attributes of the XML tag that is inflating the view

·public View (Context context, AttributeSet attrs,int defStyle)

Perform inflation from XML and apply a class-specific base style. This constructor of View allows subclasses to use their own base style when they are inflating. For example, a Button class's constructor would call this version of the super class constructor and supply R.attr.buttonStyle fordefStyle; this allows the theme's button style to modify all of the base view attributes (in particular its background) as well as the Button class's attributes.

看的不太懂,没用到,下放一下吧额

这就是为什么要添加上面的两个构造方法的原因。

有时我们为了更好的体现视觉效果 ,我们的手机APP会禁用横竖屏切换,本文我们来讲解一下Android APP如何简单快速的实现禁用横竖屏切换效果。

一般情况下APP的界面都是为竖屏设计的,一旦自动切换到横屏,界面可能就无法直视了。而且每次屏幕方向切换,当前的页面都会销毁并重新创建。

下面先做一个简单的演示

布局文件:



<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

   <TextView
        android:id="@+id/tvMsg"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content" />





后台Activity文件:


package chengyujia.androidtest;

import android.app.Activity;
import android.content.res.Configuration;
import android.os.Bundle;
import android.widget.TextView;

public class OrientationActivity extends Activity {

    private TextView tvMsg;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_orientation);
        tvMsg = (TextView) findViewById(R.id.tvMsg);
        // 默认情况下每次横竖屏切换,当前的Activity都会销毁,然后重新创建,并调用onCreate方法。
        showOrientation();
    }

    private void showOrientation() {
        if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
            tvMsg.setText("当前是横屏");
        }

        else if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {
            tvMsg.setText("当前是竖屏");
        }
    }
}



下面是运行截图:


旋转屏幕到横屏时的截图:


禁用横竖屏切换,有两种方式,第一种是在配置文件中配置,第二种是在Java代码中设置。

第一种是在AndroidManifest.xml中,为activity节点配置android:screenOrientation属性,指定该activity为竖屏或横屏,我们将上面的这个activity的配置为竖屏,如下:

<activity
            android:name="chengyujia.androidtest.OrientationActivity"
            android:screenOrientation="portrait" />

再运行测试,此时无论手机屏幕方向如何,该activity始终是竖屏的。如果android:screenOrientation="landscape" 则始终是横屏。

下面来看第二种,

只要在onCreate方法中加一句

setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);

即可始终保持竖屏,如果要横屏,代码是

setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);

如上,对于单个Activity禁用横竖屏切换是很简单的,但是实际项目中会有很多的Activity,如果每个都设置一下就太麻烦了。有没有一处设置全局有效的方法呢?答案是有的,只要对第二种方式稍微改造一下即可。我们可以写一个如下的BaseActivity类:


package chengyujia.androidtest;

import android.app.Activity;
import android.content.pm.ActivityInfo;
import android.os.Bundle;

public class BaseActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
    }
}



让其它的Activity都继承这个BaseActivity类就能实现全局禁用横竖屏切换了。实际开发中常用这种方法,不仅仅是用来设置横竖屏,还有其它的公共功能也可以写在BaseActivity中。继承是个好东西啊,哈哈。



Android横竖屏切换小结

一、禁止APP内横竖屏切换

上述设置更改的是整个手机的横竖屏切换,当手机没有关闭横竖屏切换功能时,系统一旦触发横竖屏切换,缺省状态下,当前活动的App的界面就会进行横竖屏切换,由于横竖屏的界面尺寸等参数不同,很多软件在设计和开发中为了避免横竖屏切换时引发不必要的麻烦,通常需要让App禁止掉横竖屏的切换,这就需要通过在AndroidManifest.xml中设置activity中的android:screenOrientation属性值来实现。

该android:screenOrientation属性,他有以下几个参数:

"unspecified":默认值 由系统来判断显示方向.判定的策略是和设备相关的,所以不同的设备会有不同的显示方向.

"landscape":横屏显示(宽比高要长)

"portrait":竖屏显示(高比宽要长)

"user":用户当前首选的方向

"behind":和该Activity下面的那个Activity的方向一致(在Activity堆栈中的)

"sensor":有物理的感应器来决定。如果用户旋转设备这屏幕会横竖屏切换。

"nosensor":忽略物理感应器,这样就不会随着用户旋转设备而更改了("unspecified"设置除外)。

比如下列设置

android:screenOrientation="portrait"

则无论手机如何变动,拥有这个属性的activity都将是竖屏显示。

android:screenOrientation="landscape",为横屏显示。

上述修改也可以在Java代码中通过类似如下代码来设置

setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE)


二、APP的横竖屏切换可以手动触发吗

由上面描述可知,当android:screenOrientation为默认值"unspecified"或"sensor"等时,就会有系统根据设备的旋转情况来触发横竖屏的切换,那么有没有方法我们手动在程序中触发横竖屏的变换呢,显然上面为我们提供的setRequestedOrientation就是系统提供的一个入口,下面我们给出一个按键的方式来触发的案列:

public class MainActivity extends Activity implements OnClickListener {

     private Button mBtnLandscape;

     private Button mBtnPortrait;

    

     @Override

     protected void onCreate(Bundle savedInstanceState) {

         super.onCreate(savedInstanceState);

         setContentView(R.layout.activity_main);

         mBtnLandscape = (Button) findViewById(R.id.but_landscape);

         mBtnPortrait = (Button) findViewById(R.id.but_portrait);

         mBtnLandscape.setOnClickListener(this);

         mBtnPortrait.setOnClickListener(this);

     }

 

     @Override

     public void onClick(View v) {

                 // TODO Auto-generated method stub

                 if(v == mBtnLandscape){

                             setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);

                 }else{

                             setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);

                 }

     }

    

     @Override

     public void onConfigurationChanged(Configuration newConfig) {

         super.onConfigurationChanged(newConfig);

         String message=newConfig.orientation==Configuration.ORIENTATION_LANDSCAPE ? "屏幕设置为:横屏" : "屏幕设置为:竖屏";

         Toast.makeText(this, message, Toast.LENGTH_LONG).show();

     }

}

需要注意的是,手动调用时,无视AndroidManifest中关于screenOrientation的设置;另外上述代码中的onConfigurationChanged要被调用到也是需要条件的,在这里,只给代码,不做讨论,后面再给出一个相关的补充说明。


三、重启Activity的横竖屏切换

在上面的案列中,缺省状态下,Activity每次横竖屏切换(包括用setRequestedOrientation调用)都会重新调用一轮onPause-> onStop-> onDestory-> onCreate->onStart->onResume操作,从而销毁原来的Activity对象,创建新的Activity对象,这是因为通常情况下软件在横竖屏之间切换,界面的高宽会发生转换,从而可能会要求不同的布局。具体的布局切换可以通过如下两种方法来实现:

1)在res目录下建立layout-land和layout-port目录,相应的layout文件名不变,比如main.xml。layout-land是横屏的layout,layout-port是竖屏的layout,其他的不用管,横竖屏切换时程序自己会调用Activity的onCreate方法,从而根据当前横竖屏情况自动加载响应的布局。

2)假如布局资源是不一样又不按照如上设置,则需要通过java代码来判断当前是横屏还是竖屏然后来加载相应的xml布局文件(比如mainP为竖屏mainL为横屏)。因为当屏幕变为横屏的时候,系统会重新呼叫当前Activity的onCreate方法,你可以把以下方法放在你的onCreate中来检查当前的方向,然后可以让你的setContentView来载入不同的layout xml。

@Override

protected void onCreate(Bundle icicle) {

 super.onCreate(icicle);

 int mCurrentOrientation = getResources().getConfiguration().orientation;

 if ( mCurrentOrientation == Configuration.ORIENTATION_PORTRAIT ) {

     // If current screen is portrait

     Log.i("info", "portrait"); // 竖屏

     setContentView(R.layout.mainP);

 } else if ( mCurrentOrientation == Configuration.ORIENTATION_LANDSCAPE ) {

     //If current screen is landscape

     Log.i("info", "landscape"); // 横屏

     setContentView(R.layout.mainL);

 }

 init();//初始化,赋值等操作

 findViews();//获得控件

 setListensers();//设置控件的各种监听方法

}

上面只是对布局切换做了描述,实际上由于重启Activity在未加处理的情况下必然导致数据的丢失和重新获取,这样用户体验会非常差。为此就要在切换前对数据进行保存,切换重启后对数据进行恢复,具体操作的步骤如下:

重写Activity.onRetainNonConfigurationInstance(),用户横竖屏切换前保存数据

@Override

public Object onRetainNonConfigurationInstance() {

    final MyDataObject data = collectMyLoadedData();

    return data;

}

在onCreate()函数中调用getLastNonConfigurationInstance(),获取onRetainNonConfigurationInstance()保存的数据

@Override

public void onCreate(Bundle savedInstanceState) {

    super.onCreate(savedInstanceState);

    setContentView(R.layout.main);

 

    final MyDataObject data = (MyDataObject) getLastNonConfigurationInstance();

    if (data == null) {

        data = loadMyData();

    }

    ...

}


四、非重启Activity的横竖屏切换

虽然重启Activity为我们提供了保存数据和读取数据的方式,但是如此一来程序会显得有些繁琐,所以有时候程序员往往就不想让Activity重启,Android也为我们提供了解决方案,就是通过onConfigurationChanged拦截横竖屏变换,从而进行必要的重新布局和切换操作。操作步骤如下:

首先,manifest中为相应的Activity设置android:configChanges属性,从而让Activity不延续上述的重建流程,具体如下:

Andorid 3.2以前的SDK可以使用如下配置

android:configChanges="orientation|keyboardHidden"

而Adnroid 3.2以后的SDK必须添加一个screenSize属性,具体如下

android:configChanges="keyboardHidden|orientation|screenSize"

或者

android:configChanges="orientation|screenSize"

关于configChanges的详细描述,后面有个简单补充章节,这里不做过多展开。

其次,在Activity或View的onConfigurationChanged(Configuration newConfig)函数中获取当前横竖屏参数。至于其调用顺序跟touch事件的传递顺序相似,不过他没有消费事件的概念,会顺次调用到每一个onConfigurationChanged函数。下面是重写Activity的例子:

//布局分别在layout-land和layout-port目录中的同名main.xml时

@Override

public void onConfigurationChanged (Configuration newConfig){

    super.onConfigurationChanged(newConfig);

    setContentView(R.layout.main);

    //注意,这里删除了init(),否则又初始化了,状态就丢失

    findViews();

    setListensers();

}

//布局为不按照layout-land和layout-port目录,而自定义名字时

@Override

public void onConfigurationChanged (Configuration newConfig){

    super.onConfigurationChanged(newConfig);

    int mCurrentOrientation = getResources().getConfiguration().orientation;

    if ( mCurrentOrientation == Configuration.ORIENTATION_PORTRAIT ) {

        // If current screen is portrait

        setContentView(R.layout.mainP);

        //注意,这里删除了init(),否则又初始化了,状态就丢失

        findViews();

        setListensers();

    } else if ( mCurrentOrientation == Configuration.ORIENTATION_LANDSCAPE ) {

        //If current screen is landscape

        setContentView(R.layout.mainL);

        //注意,这里删除了init(),否则又初始化了,状态就丢失

        findViews();

        setListensers();

    }

}

当然有时候连布局都不用更改的话,就可以直接对原有控件进行调用操作了,比如:

public class MainActivity extends Activity {

    private TextView textView;

    @Override

    public void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.main);

        Log.i("--Main--", "onCreate");

        textView=(TextView)findViewById(R.id.tv_id);

    }

       

    @Override

    public void onConfigurationChanged(Configuration newConfig) {

        super.onConfigurationChanged(newConfig);

        Log.i("--Main--", "onConfigurationChanged");

        if(newConfig.orientation==Configuration.ORIENTATION_LANDSCAPE){

            textView.setText("当前屏幕为横屏");

        }else{

            textView.setText("当前屏幕为竖屏");

        }

    }   

}

需要注意的是,onConfigurationChanged函数中只能获得横竖屏切换后的参数,在该函数中获取不到新的Layout和控件的尺寸位置信息,如果要处理尺寸和位置信息,必须通过消息异步或者延时调用,下面是一个App在横竖屏切换时需要重新设置popupWindow位置的代码:

@Override

protected void onConfigurationChanged(Configuration newConfig) {

    super.onConfigurationChanged(newConfig);

    //View中不用创建Handler,可直接调用post操作

    //new Handler().postDelayed(new Runnable() {

    //    @Override

    //    public void run() {

    //        updatePopup();    

    //    }

    //}, 500);

 

    postDelayed(new Runnable() {

        @Override

        public void run() {

            updatePopup();      //

        }

    }, 500);//如果不在post中,而是直接调用,那么弹出位置就会有问题

}

虽然上面没有看到对布局的显式调用进行重新布局,照理控件的对象没有被销毁,但是控件在横竖屏切换时应该是需要进行重新layout和measure,然后再进行重绘的,否则不会引发弹出框位置的变化,至于如何调用重新layout、measure和Draw操作,在这里就不多展开了。


五、对于AndroidManifest.xml设置的补充

经过上面代码演示,我们可以看到具体实现涉及到了Manifest工程配置里面具体Activity的screenOrientation和configChanges两个参数,这两个参数screenOrientation的优先级是高于configChanges,即假如screenOrientation设置为固定横竖屏时,那么configChanges参数无论怎么设都没有办法引发横竖屏切换,除非在代码中手动调用setRequestedOrientation函数进行修改。

screenOrientation属性在前面已经讲过了,而关于configChanges属性设置有如下选项:

描述

mcc

IMSI移动台的国家代码(MCC)发生变化——一个SIM被探测到并且更新MCC

mnc

IMSI移动台的网络代码(MNC)发生变化——一个SIM被探测到并且更新MNC

locale

区域发生变化——用户选择了一个文本需要显示的新语言

touchscreen

触摸屏发生变化。(这个通常不会发生。)

keyboard

键盘类型发生变化——例如:用户插入了外接键盘。

keyboardHidden

键盘的可访问性发生变化——例如:用户发现了硬件键盘。

navigation

导航类型(轨迹球或dpad)发生变化。(通常不会发生。)

screenLayout

屏幕布局发生变化——这个会导致显示不同的Activity。

fontScale

字体缩放因子发生变化——用户选择了新的字体大小。

uiMode

当UI模式发生改变的时候——当用户放置设备到桌子或/汽车或夜间模式改变的时候可以引起UI模式变化。阅读UiModeManager。在API级别8时引入。

orientation

屏幕方向发生变化——用户旋转了屏幕。注意:如果应用程序的目标API级别是13或更高(通过属性minSdkVersion和属性targetSdkVersion声明),你也需要声明配置项screenSize,因为这将在设备选择肖像和屏幕方向时发生改变。

screenSize

当前可用屏幕大小发生变化。这代表一个当前可用大小的变化,和当前的比率相关,因此当用户选择不同的画面和图像,会发生 变化。然而,如果你的程序目标API级别是12或更低,你的Activity总是会自己处理这个配置变化(这个变化不会引起Activity的重启,甚至 在Android 3.2或更新的设备上)。在API级别13里加入的。

smallestScreenSize

物理屏幕大小的变化。不管方向的变化,仅仅在实际物理屏幕打包变化的时候,如:外接显示器。这个配置项的变化引起在 smallestWidth configuration里的变化。然而,如果你的程序目标API级别是12或更低,你的Activity将自己处理这个变化(这个变化不会引起 Activity的重启,甚至在Android 3.2或更新的设备上)在API级别13里加入的。

layoutDirection

布局方向变化。例如书写方式从左向右(LTR)转换为从右向左(RTL)

 

从上述这个表我们可以看到除了横竖屏,包括语言、网络、键盘和外设等变化都可以被onConfigurationChanged函数监控到,具体的内容和释义还是查看官方英文文档吧,详见如下链接

http://developer.android.com/guide/topics/manifest/activity-element.html

中文翻译可以查阅 http://wiki.eoe.cn/page/Activity.html

结合网上的整理,小结跟这几配置相关的情景:

1、不设置Activity的android:configChanges时,切屏会重新调用各个生命周期,切横屏时会执行一次,切竖屏时会执行两次(我在三星4.0设备上发现切横屏和竖屏都是执行一次,而并非这里说的有执行两次的情况,不知道是否以前版本手机会这样?);

2、设置Activity的android:configChanges="orientation"时,切屏还是会重新调用各个生命周期,切横、竖屏时只会执行一次;

3、设置Activity的android:configChanges="orientation|keyboardHidden"时,切屏不会重新调用各个生命周期,只会执行onConfigurationChanged方法。

注:上述描述是在Android3.2以前,如果缺少了keyboardHidden选项,不能防止Activity的销毁重启,也就不能执行onConfigurationChanged方法了。在3.2之后,必须加上screenSize属性才可以屏蔽调用Activity的生命周期(我在一些设备上亲测可以不需要keyboardHidden,只要screenSize就可以了,但是保险起见还是继续保留keyboardHidden吧)。


六、对于setRequestedOrientation函数的补充说明

在上述(二)对于手动触发横竖屏切换的时候,我们用到了setRequestedOrientation,那时只是简单做了下演示,后来发现还是需要做下补充说明的:

首先在非重启Activity模式下

手动调用setRequestedOrientation之后,假如会引发横竖屏切换(即请求的横竖屏要求与当前的横竖屏情况不一致,就会引发切换),那么会立即调用onConfigurationChanged函数;假如不会引发横竖屏切换(请求前后一致),那么也就不会调用到onConfigurationChanged函数。

这个手动调用setRequestedOrientation的地方也可以在Activity中的任何地方,即也可以在onConfigurationChanged中调用,但是一旦指定为横屏或竖屏完成这个变换之后,后面不论屏幕如何进行怎么翻转变化,都不会再触发横竖屏切换了,也即等同于在manifest中设置了android:screenOrientation属性为横屏或竖屏。如果要恢复为响应横竖屏随物理传感器设备变换,那么就需要手动调用类似如下代码进行恢复:

setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR);

其次在重启Activity模式下

手动调用setRequestedOrientation发出横竖屏设定请求之后,假如需要进行横竖屏切换(即请求前后横竖屏状态不一致),则会对Activity进行销毁并重启;假如不需要需要进行横竖屏切换,则Activity维持现状不变;

手动调用setRequestedOrientation一次,完成变换之后,也跟上面非重启一样,相当于在manifest中设置了android:screenOrientation属性为横屏或竖屏。要想恢复也需要重新调用类似上面非重启的调用。

在这样一个原理下,就有了对如下一种需求的解决方案:

让App启动的时候是横屏的话就横屏表示,纵屏的话就纵屏表示,然后手机切换横竖屏就不能用了该怎么解决呢?

[!--infotagslink--]

相关文章

  • C#创建自定义控件及添加自定义属性和事件使用实例详解

    这篇文章主要给大家介绍了关于C#创建自定义控件及添加自定义属性和事件使用的相关资料,文中通过示例代码介绍的非常详细,对大家学习或者使用C#具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧...2020-06-25
  • JS实现自定义简单网页软键盘效果代码

    本文实例讲述了JS实现自定义简单网页软键盘效果。分享给大家供大家参考,具体如下:这是一款自定义的简单点的网页软键盘,没有使用任何控件,仅是为了练习JavaScript编写水平,安全性方面没有过多考虑,有顾虑的可以不用,目的是学...2015-11-08
  • android自定义动态设置Button样式【很常用】

    为了增强android应用的用户体验,我们可以在一些Button按钮上自定义动态的设置一些样式,比如交互时改变字体、颜色、背景图等。 今天来看一个通过重写Button来动态实...2016-09-20
  • Android自定义WebView网络视频播放控件例子

    下面我们来看一篇关于Android自定义WebView网络视频播放控件开发例子,这个文章写得非常的不错下面给各位共享一下吧。 因为业务需要,以下代码均以Youtube网站在线视...2016-10-02
  • 自定义jquery模态窗口插件无法在顶层窗口显示问题

    自定义一个jquery模态窗口插件,将它集成到现有平台框架中时,它只能在mainFrame窗口中显示,无法在顶层窗口显示. 解决这个问题的办法: 通过以下代码就可能实现在顶层窗口弹窗 复制代码 代码如下: $(window.top.documen...2014-05-31
  • 自定义feignClient的常见坑及解决

    这篇文章主要介绍了自定义feignClient的常见坑及解决方案,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教...2021-10-20
  • pytorch 自定义卷积核进行卷积操作方式

    今天小编就为大家分享一篇pytorch 自定义卷积核进行卷积操作方式,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2020-05-06
  • PHP YII框架开发小技巧之模型(models)中rules自定义验证规则

    YII的models中的rules部分是一些表单的验证规则,对于表单验证十分有用,在相应的视图(views)里面添加了表单,在表单被提交之前程序都会自动先来这里面的规则里验证,只有通过对其有效的限制规则后才能被提交,可以很有效地保证...2015-11-24
  • jquery自定义插件开发之window的实现过程

    这篇文章主要介绍了jquery自定义插件开发之window的实现过程的相关资料,需要的朋友可以参考下...2016-05-09
  • Vue 组件复用多次自定义参数操作

    这篇文章主要介绍了Vue 组件复用多次自定义参数操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2020-07-27
  • C#自定义事件监听实现方法

    这篇文章主要介绍了C#自定义事件监听实现方法,涉及C#事件监听的实现技巧,具有一定参考借鉴价值,需要的朋友可以参考下...2020-06-25
  • 纯javascript实现简单下拉刷新功能

    代码很简单,实现的功能却很实用,直接奉上代码CSS:复制代码 代码如下: <meta charset="utf-8" /> <title>Pull to Refresh</title> <meta name="viewport" content="width=device-width,height=device-height,inital-scal...2015-03-15
  • Ionic如何实现下拉刷新与上拉加载功能

    在日常项目开发中我们经常见到下拉刷新上拉加载的功能,接下来通过本文给大家介绍ionic如何实现下拉刷新与上拉加载的相关资料,需要的朋友可以参考下...2016-06-12
  • 使用BindingResult 自定义错误信息

    这篇文章主要介绍了使用BindingResult 自定义错误信息,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教...2021-10-23
  • 在Vue中获取自定义属性方法:data-id的实例

    这篇文章主要介绍了在Vue中获取自定义属性方法:data-id的实例,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2020-09-09
  • uniapp 仿微信的右边下拉选择弹出框的实现代码

    这篇文章主要介绍了uniapp 仿微信的右边下拉选择弹出框的实现代码,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-07-12
  • 解决vue动态下拉菜单 有数据未反应的问题

    这篇文章主要介绍了解决vue动态下拉菜单 有数据未反应的问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2020-08-06
  • pytorch 自定义参数不更新方式

    今天小编就为大家分享一篇pytorch 自定义参数不更新方式,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2020-04-29
  • DropDownList添加客户端下拉事件操作

    我们知道,DropDownList下拉框是一个服务器控件,有时候,有些朋友为了方便绑定DropDownList下拉框的选项,但又想在DropDownList实现客户端的下拉事件,那该怎么实现呢?...2021-09-22
  • thinkphp自定义权限管理之名称判断方法

    下面小编就为大家带来一篇thinkphp自定义权限管理之名称判断方法。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧...2017-04-03