月度归档: 2021 年 7 月

Android酷炫实用的开源框架

Android酷炫实用的开源框架(UI框架)

前言

忙碌的工作终于可以停息一段时间了,*近突然有一个想法,就是自己写一个app,所以找了一些合适开源控件,这样更加省时,再此分享给大家,希望能对大家有帮助,此博文介绍的都是UI上面的框架,接下来会有其他的开源框架(如:HTTP框架、DB框架)。

1.Side-Menu.Android
分类侧滑菜单,Yalantis 出品。

image
2.Context-Menu.Android
可以方便快速集成漂亮带有动画效果的上下文菜单,Yalantis出品。

image
3.Pull-to-Refresh.Rentals-Android
提供一个简单可以自定义的下拉刷新实现,Yalantis 出品。

image
4.Titanic
可以显示水位上升下降的TextView

image
5.AndroidSwipeLayout
滑动Layout,支持单个View,ListView,GridView

image
6.Android Typeface Helper
可以帮你轻松实现自定义字体的库

image
7.android-lockpattern
Android的图案密码解锁

 

APP示例:Android开机的图案密码解锁,支付宝的密码解锁
image
8.ToggleButton
状态切换的 Button,类似 iOS,用 View 实现

image
9.WilliamChart
绘制图表的库,支持LineChartView、BarChartView和StackBarChartView三中图表类型,并且支持 Android 2.2及以上的系统。

image
10.实现滑动ViewPager渐变背景色

image
11.Euclid
用户简历界面,Yalantis 出品。

image
12.InstaMaterial
Instagram的一组Material 风格的概念设计

image
13.SpringIndicator

使用bezier实现粘连效果的页面指示

image
14.BezierDemo

仿qq消息气泡拖拽消失的效果。

image
15.FoldableLayout

折叠的信纸被打开一样的动画效果

image
16.Taurus
下拉刷新,Yalantis 出品。(是不是有点似曾相识呢?)

image
17.PersistentSearch

在点击搜索的时候控件在原有位置显示输入框。

image
18.circular-progress-button

带进度显示的Button

image
19.discrollview

当上下滚动的时候子元素会呈现不同动画效果的scrollView,网页上称之为:视差滚动

image
20.sweet-alert-dialog

一个带动画效果的自定义对话框样式

image
21.android-floating-action-button

Material Desig风格的浮动操作按钮

image
22.android-collapse-calendar-view

可以在月视图与周视图之间切换的calendar控件

image
23.NumberProgressBar

个简约性感的数字进度条

image
24.CircularProgressView

CircularProgressView 是通过自定义view的方式实现的Material风格的加载提示控件,兼容任何版本。

image
25.OriSim3D-Android

opengl 实现了各种折纸效果,模拟了从一张纸折叠成一条船的整个过程

image

 

iOS方法名称混淆之随机字符串和随机单词组合

iOS方法名的混淆可以使用宏定义的方式,方便管理。
但是目前混淆成随机字母的组合很难上架,会被Apple警告语意不明确。因此可以尝试通过单词组合的方式来混淆,通过读取单词库,获取不同的单词组合来实现混淆方法名。

通过shell脚本实现,具体代码如下:

TABLENAME=symbols
#混淆时生成的数据库文件
SYMBOL_DB_FILE=”./symbols”
#需要混淆的方法名称
STRING_SYMBOL_FILE=”./func.list”
#混淆后生成的宏定义
HEAD_FILE=”./codeObfuscation.h”
#单词库文件,一行1个单词
WORDS_FILE=”./words.txt”
export LC_CTYPE=C

#维护数据库方便日后作排重
createTable()
{
echo “create table $TABLENAME(src text, des text);” | sqlite3 $SYMBOL_DB_FILE
}

insertValue()
{
echo “insert into $TABLENAME values(‘$1′ ,’$2′);” | sqlite3 $SYMBOL_DB_FILE
}

query()
{
echo “select * from $TABLENAME where src=’$1’;” | sqlite3 $SYMBOL_DB_FILE
}

#随机字符串
ramdomString()
{
openssl rand -base64 64 | tr -cd ‘a-zA-Z’ |head -c 16
}

#字符串首字母大写
capitalizedString() {
str=$1
firstLetter=`echo ${str:0:1} | awk ‘{print toupper($0)}’`
otherLetter=${str:1}
result=$firstLetter$otherLetter
echo $result
}

#从单词库中获取随机字符串
ramdomString2()
{
list=(`cat $WORDS_FILE`)
#1000为单词库中的单词个数,不能超过总数
randomIndex1=$[$RANDOM%1000+1]
words1=${list[$randomIndex1]}
#取出来第2个单词并将首字母大写
randomIndex2=$[$RANDOM%1000+1]
words2=${list[$randomIndex2]}
newWords2=$(capitalizedString $words2)
#将2个单词拼接,也可以3个、4个等更多
totalWords=$words1$newWords2
echo $totalWords
}

rm -f $SYMBOL_DB_FILE
rm -f $HEAD_FILE
createTable

touch $HEAD_FILE
echo ‘#ifndef Demo_codeObfuscation_h
#define Demo_codeObfuscation_h’ >> $HEAD_FILE
echo “//confuse string at `date`” >> $HEAD_FILE
cat “$STRING_SYMBOL_FILE” | while read -ra line; do
if [[ ! -z “$line” ]]; then
ramdom=$(ramdomString2)
#ramdom=`ramdomString2`
insertValue $line $ramdom
echo “#define $line $ramdom” >> $HEAD_FILE
fi
done
echo “#endif” >> $HEAD_FILE

sqlite3 $SYMBOL_DB_FILE .dump

方法列表文件示例:

图1:

%title插图%num

单词库文件示例:

图1:

%title插图%num

生成的宏定义示例:

图1:

%title插图%num

Unity适配iOS14时app闪退原因之一

*近在iOS14设备上测试Unity游戏(App类似),由于之前没注意修改info.plist中BundleName属性,一直写的是中文名称,在iOS14之前的设备上没有闪退情况,但是换到iOS14设备后就出现闪退情况。

解决方法:info.plist中的BundleName属性必须要设置为英文,BundleDisplayName属性设置成中文没有问题。

Android动画框架之视图动画基本使用

之前在学习View 滑动的时候其实就是View动画的一种展现形式了,当时介绍了有7中方法可以实现View的滑动,截止目前还有Sroller,动画以及ViewDragHelper实现没有介绍,今天的内容里面就包含了部分View的滑动实现,这里说部分主要有两个原因:

今天介绍的视图动画实现的滑动是不可交互的滑动实现
后面可以通过3.0之后的属性动画完整实现

1,基础知识
好的,那几天就来简单看一下Android动画框架中的视图动画的基本使用。首先,先来看一个效果图,里面包含了视图动画可以实现的独立动画效果

%title插图%num

从上面的效果图可以看出,视图动画API主要可以帮助我们实现View的以下几个电话效果:

透明度变化
旋转(围绕一个坐标点)
伸缩(围绕一个坐标点)
平移
对应的API分别为:

AlphaAnimation
AlphaAnimation alphaAnimation1=new AlphaAnimation(1,0);
alphaAnimation1.setDuration(timeLength);
RotateAnimation
RotateAnimation rotateAnimation1=new RotateAnimation(0,360,Animation.RELATIVE_TO_SELF,0.5f,Animation.RELATIVE_TO_SELF,0.5f);
rotateAnimation1.setDuration(timeLength);
ScaleAnimation
ScaleAnimation scaleAnimation=new ScaleAnimation(0,1,0,1, Animation.RELATIVE_TO_SELF,0.5f,Animation.RELATIVE_TO_SELF,0.5f);
scaleAnimation.setDuration(timeLength);
TranslateAnimation
TranslateAnimation translateAnimation=new TranslateAnimation(startX,endX,startY,endY);
translateAnimation.setDuration(timeLength);
这些API都是Animation的子类,文档如下:

android.view.animation
类 Animation
java.lang.Object
android.view.animation.Animation
所有已实现的接口:

Cloneable

直接已知子类:

AlphaAnimation, AnimationSet, RotateAnimation, ScaleAnimation, TranslateAnimation

由文档可以看出,它还有一个子类是AnimationSet,顾名思义就是动画集合的意思,也就是说添加一组动画到View,然后这些动画效果同时展示在View上,这样就会有一个组合动画的效果。比如:

AnimationSet set=new AnimationSet(true);
……
set.addAnimation(alphaAnimation1);
set.addAnimation(alphaAnimation2);
set.addAnimation(rotateAnimation1);
set.addAnimation(scaleAnimation);
set.addAnimation(translateAnimation);
testImage.startAnimation(set);
此外,针对每一个动画对象,我们都可以为其添加动画状态监听,这样就可以在动画开始和结束的时候做一些逻辑操作,比如:

scaleAnimation.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {

}

@Override
public void onAnimationEnd(Animation animation) {
Toast.makeText(MainActivity.this,”平移动画开始”,Toast.LENGTH_SHORT).show();
testImage.startAnimation(translateAnimation);
}

@Override
public void onAnimationRepeat(Animation animation) {

}
});
针对每一个动画对象,我们都可以在动画的三种状态的时候做逻辑操作:

start
end
repeat

2 案例实现
实现步骤:

*步:构建布局,一个按钮,两个ImageView并且重叠,下面一张ImageView主要用于记录上面一张ImageView的初始位置,方便后面验证视图动画完成的知识View显示的变化,交互时间还是停留在初始位置

第二步:获取动画对象,设置状态监听,实现动画间平滑衔接,并吐司信息

第三步:根据展示效果给ImageView添加动画

代码:

activity_main.xml代码:

<?xml version=”1.0″ encoding=”utf-8″?>
<android.support.constraint.ConstraintLayout xmlns:android=”http://schemas.android.com/apk/res/android”
xmlns:app=”http://schemas.android.com/apk/res-auto”
xmlns:tools=”http://schemas.android.com/tools”
android:layout_width=”match_parent”
android:layout_height=”match_parent”
tools:context=”com.hfut.operationanimation.MainActivity”>

<Button
android:id=”@+id/controll_button”
android:layout_marginTop=”30dp”
android:textSize=”30dp”
android:text=”动画测试”
android:onClick=”animationTest”
app:layout_constraintLeft_toLeftOf=”parent”
app:layout_constraintRight_toRightOf=”parent”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content” />

<ImageView
android:layout_marginTop=”30dp”
android:id=”@+id/test_image_background”
app:layout_constraintTop_toBottomOf=”@+id/controll_button”
app:layout_constraintLeft_toLeftOf=”parent”
app:layout_constraintRight_toRightOf=”parent”
android:src=”@color/colorAccent”
android:layout_width=”180dp”
android:layout_height=”240dp” />
<ImageView
android:layout_marginTop=”30dp”
android:id=”@+id/test_image”
app:layout_constraintTop_toBottomOf=”@+id/controll_button”
app:layout_constraintLeft_toLeftOf=”parent”
app:layout_constraintRight_toRightOf=”parent”
android:src=”@drawable/testimage”
android:layout_width=”180dp”
android:layout_height=”240dp” />

</android.support.constraint.ConstraintLayout>

MainActivity代码:

package com.hfut.operationanimation;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
import android.view.animation.AnimationSet;
import android.view.animation.RotateAnimation;
import android.view.animation.ScaleAnimation;
import android.view.animation.TranslateAnimation;
import android.widget.ImageView;
import android.widget.Toast;

/**
* @author why
* @date 2018-9-7 21:22:18
*/
public class MainActivity extends AppCompatActivity {

ImageView testImage;
int startX;
int startY;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
testImage=findViewById(R.id.test_image);
//监听当前Image的点击事件
testImage.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(MainActivity.this,”Image被点击了”,Toast.LENGTH_SHORT).show();
}
});

startX=(int)testImage.getX();
startY=(int)testImage.getY();
}

public void animationTest(View view){

//动画集合对象
final AnimationSet set=new AnimationSet(true);

//透明度动画
AlphaAnimation alphaAnimation1=new AlphaAnimation(1,0);
alphaAnimation1.setDuration(1000);
final AlphaAnimation alphaAnimation2=new AlphaAnimation(0,1);
alphaAnimation2.setDuration(1000);
//旋转动画
final RotateAnimation rotateAnimation1=new RotateAnimation(0,360,Animation.RELATIVE_TO_SELF,0.5f,Animation.RELATIVE_TO_SELF,0.5f);
rotateAnimation1.setDuration(2000);
//缩放动画
final ScaleAnimation scaleAnimation=new ScaleAnimation(0,1,0,1, Animation.RELATIVE_TO_SELF,0.5f,Animation.RELATIVE_TO_SELF,0.5f);
scaleAnimation.setDuration(2000);
//平移动画
final TranslateAnimation translateAnimation=new TranslateAnimation(startX,200,startY,300);
translateAnimation.setDuration(2000);
//设置平移后是否留在当前位置
translateAnimation.setFillAfter(true);

translateAnimation.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {

}

@Override
public void onAnimationEnd(Animation animation) {
Toast.makeText(MainActivity.this,”平移动画结束”,Toast.LENGTH_SHORT).show();
// try {
// Thread.sleep(1000);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// testImage.startAnimation(set);
}

@Override
public void onAnimationRepeat(Animation animation) {

}
});

scaleAnimation.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {

}

@Override
public void onAnimationEnd(Animation animation) {
Toast.makeText(MainActivity.this,”平移动画开始”,Toast.LENGTH_SHORT).show();
testImage.startAnimation(translateAnimation);
}

@Override
public void onAnimationRepeat(Animation animation) {

}
});

rotateAnimation1.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {

}

@Override
public void onAnimationEnd(Animation animation) {
Toast.makeText(MainActivity.this,”缩放动画开始”,Toast.LENGTH_SHORT).show();
testImage.startAnimation(scaleAnimation);
}

@Override
public void onAnimationRepeat(Animation animation) {

}
});

alphaAnimation2.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {

}

@Override
public void onAnimationEnd(Animation animation) {
Toast.makeText(MainActivity.this,”旋转动画开始”,Toast.LENGTH_SHORT).show();
testImage.startAnimation(rotateAnimation1);
}

@Override
public void onAnimationRepeat(Animation animation) {

}
});
//View开启动画
testImage.startAnimation(alphaAnimation1);
alphaAnimation1.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
Toast.makeText(MainActivity.this,”透明度动画开始”,Toast.LENGTH_SHORT).show();
}

@Override
public void onAnimationEnd(Animation animation) {
testImage.startAnimation(alphaAnimation2);
}

@Override
public void onAnimationRepeat(Animation animation) {

}
});

// set.addAnimation(alphaAnimation1);
// set.addAnimation(alphaAnimation2);
// set.addAnimation(rotateAnimation1);
// set.addAnimation(scaleAnimation);
// set.addAnimation(translateAnimation);
// testImage.startAnimation(set);
}
}

这里说几点注意事项

上面的示例旋转和伸缩使用的坐标是相对参数,我们也可以使用*对坐标加上View自身宽高实现,可参见Android中实现滑动(上)—-基础知识,这样就可以理解如何获取当前View的*对坐标,下面就是计算View中心点*对坐标的简易表达式:
rotateX=x+view.width/2;

rotateY=y+view.height/2;

平移动画完成后,如果不设置其停留在平移后的当前位置,则默认会返回初始位置
在验证ImageView交互位置未移动的现象,需要给ImageView设置一下点击之后的逻辑
​​​​​​​到这里,Android中关于视图动画的基本使用就简单介绍到这里了,使用其实还是蛮简单的。
————————————————

Android中实现滑动-实现(2)

在了解了前面介绍的基础知识之后,下面就来看看具体的滑动实现,接下来会介绍7种方法,主要是结合《Android群英传》中学习的知识展开。这七种方法分别是:

(1)View绘制时的layout方法

(2)View绘制时根据系统封装好的offSetLeftAndRight以及offSetTopAndBottom接口实现View的布局定位

(3)通过View的布局参数layoutParams来重置View的margin值实现布局重新定位

(4)通过View的scrollTo和scrollBy方法

(5)通过Scroller类实现

(6)通过属性动画实现

(7)通过ViewDragHelper实现

对于滑动的实现,我会分为四个部分讲解,*部分讲述(1)(2)(3)的实现方法,第二部分讲述(4)和(5)的实现,第三部分讲述属性动画方法实现,第四部分也是较复杂的部分讲述(7)的实现,下面我们就开始吧。本部分主要介绍scrollTo和scrollBy以及Scroller使用

1,scrollTo和scrollBy
任何一个View子类对象都可以调用这两个接口

1.1 两者的关系
scrollBy源码:

/**
* Move the scrolled position of your view. This will cause a call to
* {@link #onScrollChanged(int, int, int, int)} and the view will be
* invalidated.
* @param x the amount of pixels to scroll by horizontally
* @param y the amount of pixels to scroll by vertically
*/
public void scrollBy(int x, int y) {
scrollTo(mScrollX + x, mScrollY + y);
}
所以scrollBy实现的本质也是scrollTo

1.2 两者的作用和区别
(1)scrollTo(),view移动到指定坐标点

(2)scrollBy(),view依据当前的坐标点结合传入的偏移量进行移动

移动的效果:

如果是ViewGroup,那么屏幕展示的移动效果实际上是移动的其子view

如果是ImageView,Button这样的控件,那么屏幕展示的移动效果实际上是移动的其Content

比如下图示例:

%title插图%num

我们移动的View,本质上移动的却是其Content,所以如果我们想实现移动一个View的效果,只需要移动其父布局即可。需要注意的是这里有一个相对的概念,也即子View向左移动,父布局就需要向右移动;上下也是同理。下面就来看看具体的示例:

2,代码示例
2.1 简单移动测试
ScrollActivity代码:

package com.hfut.operationscroll;

import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Button;
import android.widget.LinearLayout;

/**
* @author why
* @date 2018-8-19 16:48:41
*/
public class ScrollActivity extends AppCompatActivity {

int lastX = 0;
int lastY = 0;
private static final String TAG = “MainActivity”;
Button button;
LinearLayout linearLayout;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_scroll);
button = findViewById(R.id.test_button);
linearLayout=findViewById(R.id.test_layout);
ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
actionBar.hide();
}
button.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
lastX = x;
lastY = y;
break;
case MotionEvent.ACTION_UP:
break;
case MotionEvent.ACTION_MOVE:
Log.d(TAG, “onTouch: “+ System.currentTimeMillis());
//方式一,通过视图坐标系计算偏移量
int offX = x – lastX;
int offY = y – lastY;
v.scrollBy(-offX,-offY);
linearLayout.scrollBy(-offX,-offY);
break;
default:
break;
}
return true;
}
});
}

}
activity_scroll.xml代码:

<?xml version=”1.0″ encoding=”utf-8″?>
<LinearLayout xmlns:android=”http://schemas.android.com/apk/res/android”
xmlns:app=”http://schemas.android.com/apk/res-auto”
xmlns:tools=”http://schemas.android.com/tools”
android:layout_width=”match_parent”
android:layout_height=”match_parent”
android:id=”@+id/test_layout”
android:orientation=”vertical”
tools:context=”.MainActivity”>

<Button
android:id=”@+id/test_button”
android:layout_width=”400dp”
android:layout_height=”400dp”
android:text=”拖我移动” />

</LinearLayout>
代码和*部分差不多,偏移量的计算都一样,其中:

case MotionEvent.ACTION_MOVE:
Log.d(TAG, “onTouch: “+ System.currentTimeMillis());
int offX = x – lastX;
int offY = y – lastY;
v.scrollBy(-offX,-offY);
linearLayout.scrollBy(-offX,-offY);
break;
部分,里面两行关于移动的代码,如果把*行注释掉,那么按钮和里面的text一起同步移动,如果两行全放开,那么按钮里面的text也会相对button边框移动,运行代码:

%title插图%num

v.scrollBy(-offX,-offY);
注释后:

%title插图%num

注释前:

%title插图%num

2.2 使用scrollBy或者scrollTo模拟平滑滑动
我们知道,使用这两个接口实现的滑动都是瞬间完成的,给客户的体验很不好,下面就结合这两个方法以及Timer和Handler实现一个平滑的滑动,因为都是基础知识,这里直接给出代码:

ScrollActivity代码:

package com.hfut.operationscroll;

import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.Scroller;
import android.widget.Switch;

import java.sql.Time;
import java.util.Timer;
import java.util.TimerTask;

/**
* @author why
* @date 2018-8-19 17:14:10
*/
public class ScrollActivity extends AppCompatActivity {

public static final int DESTINATION=1;
public static final int STARTPOINT=0;
int quickCount = 1;
int slowCount = 1;
LinearLayout quickLinearLayout;
LinearLayout slowLinearLayout;
Switch slowOne;
Switch quickOne;
Scroller scroller;
Button quickTrigger;
Button slowTrigger;
int movedDis = 0;
int x = 0;
int increment = 10;

Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case DESTINATION:
slowTrigger.setText(“回来”);
slowOne.setChecked(true);
break;
case STARTPOINT:
slowTrigger.setText(“慢移”);
slowOne.setChecked(false);
break;
default:
break;
}
}
};

private static final String TAG = “ScrollerActivity”;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_scroller);
quickLinearLayout = findViewById(R.id.quick_move_layout);
slowLinearLayout = findViewById(R.id.slow_move_layout);
slowOne = findViewById(R.id.slow_runner);
quickOne = findViewById(R.id.quick_runner);
quickTrigger = findViewById(R.id.quick_button);
slowTrigger = findViewById(R.id.slow_button);
x = (int) quickOne.getX();
}

public void quickMove(View view) {

if (quickCount % 2 == 1) {
//Log.d(TAG, “quickMove: ” + total);
//考虑到其本身的长度,所以这里减少了100sp
quickLinearLayout.scrollBy(-(650 – x – 100), 0);
quickOne.setChecked(true);
quickTrigger.setText(“回来”);
} else {
quickLinearLayout.scrollBy(650 – x – 100, 0);
quickOne.setChecked(false);
quickTrigger.setText(“瞬移”);
}
quickCount++;
}

public void slowMove(View view) {
if (slowCount % 2 == 1) {
movedDis = 0;
final Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
if (540 – x >= movedDis) {
movedDis += increment;
slowLinearLayout.scrollBy(-increment, 0);
} else {
Message message=new Message();
message.what=DESTINATION;
handler.sendMessage(message);
timer.cancel();
}
}
}, 200, 50);
} else {
movedDis = 0;
final Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
if (540 – x >= movedDis) {
movedDis += increment;
slowLinearLayout.scrollBy(increment, 0);
} else {
Message message=new Message();
message.what=STARTPOINT;
handler.sendMessage(message);
timer.cancel();
}
}
}, 200, 50);
}
slowCount++;
}
}

activity_scroll.xml代码:

<?xml version=”1.0″ encoding=”utf-8″?>
<LinearLayout xmlns:android=”http://schemas.android.com/apk/res/android”
xmlns:app=”http://schemas.android.com/apk/res-auto”
xmlns:tools=”http://schemas.android.com/tools”
android:layout_width=”match_parent”
android:layout_height=”match_parent”
android:orientation=”vertical”
tools:context=”com.hfut.operationscroll.ScrollActivity”>

<LinearLayout
android:layout_width=”match_parent”
android:layout_height=”wrap_content”>
<Button
android:text=”瞬移”
android:id=”@+id/quick_button”
android:onClick=”quickMove”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content” />
<LinearLayout
android:id=”@+id/quick_move_layout”
android:layout_width=”match_parent”
android:layout_height=”wrap_content”>
<Switch
android:id=”@+id/quick_runner”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:layout_marginTop=”15sp” />
</LinearLayout>

</LinearLayout>

<LinearLayout
android:layout_width=”match_parent”
android:layout_height=”wrap_content”>
<Button
android:text=”慢移”
android:id=”@+id/slow_button”
android:onClick=”slowMove”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content” />

<LinearLayout
android:id=”@+id/slow_move_layout”
android:layout_width=”match_parent”
android:layout_height=”wrap_content”>

<Switch
android:id=”@+id/slow_runner”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:layout_marginTop=”15sp” />
</LinearLayout>

</LinearLayout>

</LinearLayout>

运行结果:

%title插图%num

这部分主要还是理解使用这种移动的原理,这对于后面的难点滑动冲突问题的解决至关重要。
————————————————

iPhone连接APPSTORE导致断网现象的分析

*近一个朋友买了个iphone ,其他都很好,但在家里连无线网时一旦打开APPSTORE就会导致整个家里的网无法连上互联网,此时路由器的管理界面也打不开,只有重启路由器才能重新联网。刚开始跟我说这个问题的时候我上网查了一下,估计是路由器问题,他自己也查了,我觉得换个路由器就能解决,也就没在意。后来他告诉我因为怕换了路由器还是解决不了所以一直没换,我才开始对这个问题产生兴趣。

出现此问题的现象是iphone连上无线网,连接手机的APPSTORE或者SIRI就会导致无法联网,此时无法进入路由器管理界面,重启路由器可解决。非手机问题,因为另一部iphone也出现此现象。同时,在电脑上自建无线网用手机连接就不会出现这种问题。网上有一些人也有这种问题,但基本上找不到解决办法。改过DNS,不行。由于自建网络没问题,那么很可能就是路由器本身的问题了。于是现在开始梳理一下大概的流程:

1.手机连上路由器网络,正常。
2.手机打开APPSTORE,系统向DNS发出解析请求,解析出需要连接的IP,但在这步出现问题了,路由器断网了。

由于更换过DNS,所以不是DNS的问题。如果是在连接DNS之前出的问题,那么就是系统将网址发送到路由器之后路由器就出现崩溃无法联网。这种情况有可能是系统发送的代码与路由器固件不兼容,路由器在接收到代码后出现了崩溃。另一种可能是路由器成功向DNS服务器发出了解析请求,DNS返回了正常的代码,路由器固件与此代码不兼容,出现崩溃。

由于只是连接APPSTORE和SIRI会出现这个问题,所以猜测前者的概率大一些,毕竟DNS返回的代码区别应该不大,但APPSTORE和SIRI发送的请求代码和其他软件应该是不一样的。

Android中实现滑动-实现(1)

在了解了前面介绍的基础知识之后,下面就来看看具体的滑动实现,接下来会介绍7种方法,主要是结合《Android群英传》中学习的知识展开。这七种方法分别是:

(1)View绘制时的layout方法

(2)View绘制时根据系统封装好的offSetLeftAndRight以及offSetTopAndBottom接口实现View的布局定位

(3)通过View的布局参数layoutParams来重置View的margin值实现布局重新定位

(4)通过View的scrollTo和scrollBy方法

(5)通过Scroller类实现

(6)通过属性动画实现

(7)通过ViewDragHelper实现

对于滑动的实现,我会分为四个部分讲解,*部分讲述(1)(2)(3)的实现方法,第二部分讲述(4)和(5)的实现,第三部分讲述属性动画方法实现,第四部分也是较复杂的部分讲述(7)的实现,下面我们就开始吧。

1,View滑动的本质
view滑动的本质就是view根据实时的当前位置以及上一次的位置进行不停的重新绘制工作,比如下图:

%title插图%num

我们只需要获取到偏移量之后就可以对view进行重新绘制,也即这里面的offX和offY,而获取方式主要有两种,这两种主要是针对*部分中介绍的两种坐标系计算出来的:

(1)视图坐标系

int x = (int) event.getX();
int y = (int) event.getY();
//方式一,通过视图坐标系计算偏移量
int offX = x – lastX;
int offY = y – lastY;
(2)Android坐标系

int rawX=(int)event.getRawX();
int rawY=(int)event.getRawY();
//方式二,通过Android坐标系计算偏移量
int offRawX=rawX-lastRawX;
int offRawY=rawY-lastRawY;
2,具体实现
//方式一,通过view的layout方法实现确定绘制的坐标
v.layout(offX+v.getLeft(),offY+v.getTop(),v.getRight()+offX,v.getBottom()+offY);
//v.layout(offRawX+v.getLeft(),offRawY+v.getTop(),v.getRight()+offRawX,v.getBottom()+offRawY);
//方式二,通过系统针对偏移量确定view坐标封装的API实现
//v.offsetLeftAndRight(offX);
//v.offsetTopAndBottom(offY);
//方式三,通过设置父布局view在父布局中参数设置实现,也即是通过修改移动目标view的margin值实现
LinearLayout.LayoutParams layoutParams= (LinearLayout.LayoutParams) v.getLayoutParams();
layoutParams.leftMargin=v.getLeft()+offX;
layoutParams.topMargin=v.getTop()+offY;
v.setLayoutParams(layoutParams);
3,示例代码
MainActivity代码:

package com.hfut.operationscroll;

import android.content.Context;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.util.TypedValue;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Button;
import android.widget.LinearLayout;

/**
* @author why
* @date 2018-8-19 9:15:28
*/
public class MainActivity extends AppCompatActivity {

int lastX = 0;
int lastY = 0;
// int lastRawX=0;
// int lastRawY=0;
private static final String TAG = “MainActivity”;
Button button;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
button = findViewById(R.id.test_button);
ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
actionBar.hide();
}
button.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();

// int rawX=(int)event.getRawX();
// int rawY=(int)event.getRawY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
lastX = x;
lastY = y;
// lastRawX=rawX;
// lastRawY=rawY;
break;
case MotionEvent.ACTION_UP:
break;
case MotionEvent.ACTION_MOVE:
//方式一,通过视图坐标系计算偏移量
int offX = x – lastX;
int offY = y – lastY;
//方式二,通过Android坐标系计算偏移量
//int offRawX=rawX-lastRawX;
//int offRawY=rawY-lastRawY;

//方式一,通过view的layout方法实现确定绘制的坐标
//v.layout(offX+v.getLeft(),offY+v.getTop(),v.getRight()+offX,v.getBottom()+offY);
//v.layout(offRawX+v.getLeft(),offRawY+v.getTop(),v.getRight()+offRawX,v.getBottom()+offRawY);

//方式二,通过系统针对偏移量确定view坐标封装的API实现
//v.offsetLeftAndRight(offX);
//v.offsetTopAndBottom(offY);

//方式三,通过设置父布局view在父布局中参数设置实现,也即是通过修改移动目标view的margin值实现
LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) v.getLayoutParams();
layoutParams.leftMargin = v.getLeft() + offX;
layoutParams.topMargin = v.getTop() + offY;
v.setLayoutParams(layoutParams);

//在使用Android坐标系计算偏移量注意重置初始值
//lastRawX=rawX;
//lastRawX=rawY;
break;
default:
break;
}
return true;
}
});
}

}

activity_main.xml代码:

<?xml version=”1.0″ encoding=”utf-8″?>
<LinearLayout xmlns:android=”http://schemas.android.com/apk/res/android”
xmlns:app=”http://schemas.android.com/apk/res-auto”
xmlns:tools=”http://schemas.android.com/tools”
android:layout_width=”match_parent”
android:layout_height=”match_parent”
android:orientation=”vertical”
tools:context=”.MainActivity”>
<Button
android:id=”@+id/test_button”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:text=”拖我移动” />

</LinearLayout>

4,注意事项
(1)在使用*对坐标(Android坐标系)的时候,一定要注意重置上一次的坐标信息

//在使用Android坐标系计算偏移量注意重置初始值
//lastRawX=rawX;
//lastRawX=rawY;
(2)在使用View布局在父布局中的参数设置margin值的时候需要注意两点:

a,参数的类型根据父布局的不同而不同,比如这里的Button父布局是一个LinearLayout:

LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) v.getLayoutParams();
b,这种方式如果父布局中添加了一下属性影响到margin的值的时候,*终的效果也会不一样,比如你可以尝试把activity_main.xml文件改成如下试一试:

<?xml version=”1.0″ encoding=”utf-8″?>
<LinearLayout xmlns:android=”http://schemas.android.com/apk/res/android”
xmlns:app=”http://schemas.android.com/apk/res-auto”
xmlns:tools=”http://schemas.android.com/tools”
android:layout_width=”match_parent”
android:layout_height=”match_parent”
android:orientation=”vertical”
android:gravity=”center”
tools:context=”.MainActivity”>
<Button
android:id=”@+id/test_button”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:text=”拖我移动” />

</LinearLayout>
注:欢迎扫码关注
————————————————

Android中实现滑动(上)—-基础知识

Android当中的滑动效果可以有效改善之前的点击,长按等UI体验,今天就来看一看其具体的实现,下面将会分为两个部分展开介绍,*部分介绍一下基础知识,第二部分介绍具体的几种实现方式。

1,Android中的坐标系
1.1 Android坐标系
如下图所示,Android中的坐标系是向右为X轴正方向,向下为Y轴正方向;其中(0,0)坐标对应于手机屏幕的左上角

%title插图%num

1.2 视图坐标系
如下图所示,其中(0,0)代表父布局的左上角(嵌套同理)

%title插图%num

2,触控事件
MotionEvent对于用户与界面的交互来说必不可少,当然随着语音识别等AI技术的成熟,这种依赖会降低,目前我们在做的机器人产品用户与机器人交互主要是通过ASR和TTS以及人脸识别等,界面更多的是展示功能。其中MotionEvent中定义了很多事件常量,主要如下:

MotionEvent.ACTION_DOWN
MotionEvent.ACTION_UP
MotionEvent.ACTION_MOVE
MotionEvent.ACTION_CANCEL
MotionEvent.ACTION_OUTSIDE
MotionEvent.ACTION_POINTER_DOWN
MotionEvent.ACTION_POINTER_UP
MotionEvent.ACTION_BUTTON_PRESS
MotionEvent.ACTION_HOVER_ENTER
MotionEvent.ACTION_HOVER_EXIT
MotionEvent.ACTION_HOVER_MOVE
MotionEvent.ACTION_MASK
MotionEvent.ACTION_POINTER_INDEX_MASK
MotionEvent.ACTION_POINTER_INDEX_SHIFT
MotionEvent.ACTION_BUTTON_RELEAS
等,我们只需要在获取控件的时候设置onTouchEvent(EventMotion event)即可根据event收到的事件类型来做业务处理

3,获取坐标
3.1 通过View
getLeft() //获取View的左边到其父布局左边距离

getTop() //获取View的上边到其父布局上边距离

getRight() //获取View的右到其父布局左边距离

getBottom() //获取View的底边到其父布局上边边距离

3.2 通过MotionEvent
getX() //点击事件位置距离控件自身左边的距离

getY() //点击事件位置距离控件自身上边的距离

getRawX() //点击事件位置距离控件屏幕左边的距离

getRawY() //点击事件位置距离控件屏幕上边的距离

4,示例代码和解释
4.1 代码
MainActivity代码:

package com.hfut.operationscrollpre;

import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Button;
import android.widget.LinearLayout;

/**
* @author why
* @date 2018-8-18 15:15:13
*/
public class MainActivity extends AppCompatActivity {

private static final String TAG = “MainActivity”;
Button button;
LinearLayout linearLayout;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
button = findViewById(R.id.test_button);
linearLayout = findViewById(R.id.test_layout);

// ActionBar actionBar=getSupportActionBar();
// if(actionBar!=null){
// actionBar.hide();
// }

button.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.d(“按钮:”, “onTouch: MotionEvent.ACTION_DOWN”);
Log.d(“按钮:点击坐标(相对控件本身):”, “onTouch: x,” + event.getX() + “;y,” + event.getY());
Log.d(“按钮:点击坐标(相对上层布局):”, “onTouch: Left,” + v.getLeft() + “;Top,” + v.getTop() + “;Right,” + v.getRight()
+ “;Bottom,” + v.getBottom());
Log.d(“按钮:点击坐标(相对整个屏幕):”, “onTouch: X,” + event.getRawX() + “;Y,” + event.getRawY());
break;
case MotionEvent.ACTION_UP:
Log.d(“按钮:”, “onTouch: MotionEvent.ACTION_UP”);
Log.d(“按钮:点击坐标(相对控件本身):”, “onTouch: x,” + event.getX() + “;y,” + event.getY());
Log.d(“按钮:点击坐标(相对上层布局):”, “onTouch: Left,” + v.getLeft() + “;Top,” + v.getTop() + “;Right,” + v.getRight()
+ “;Bottom,” + v.getBottom());
Log.d(“按钮:点击坐标(相对整个屏幕):”, “onTouch: X,” + event.getRawX() + “;Y,” + event.getRawY());
break;
case MotionEvent.ACTION_MOVE:
Log.d(“按钮:”, “onTouch: MotionEvent.ACTION_MOVE”);
break;
case MotionEvent.ACTION_CANCEL:
Log.d(“按钮:”, “onTouch: MotionEvent.ACTION_CANCEL”);
break;
case MotionEvent.ACTION_OUTSIDE:
Log.d(“按钮:”, “onTouch: MotionEvent.ACTION_OUTSIDE”);
break;
case MotionEvent.ACTION_POINTER_DOWN:
Log.d(“按钮:”, “onTouch: MotionEvent.ACTION_POINTER_DOWN”);
break;
case MotionEvent.ACTION_POINTER_UP:
Log.d(“按钮:”, “onTouch: MotionEvent.ACTION_POINTER_UP”);
break;
case MotionEvent.ACTION_BUTTON_PRESS:
Log.d(“按钮:”, “onTouch: MotionEvent.ACTION_BUTTON_PRESS”);
break;
case MotionEvent.ACTION_BUTTON_RELEASE:
Log.d(“按钮:”, “onTouch: MotionEvent.ACTION_BUTTON_RELEASE”);
break;
default:
break;
}
return true;
}
});

linearLayout.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.d(“父布局:”, “onTouch: MotionEvent.ACTION_DOWN”);
Log.d(“父布局:点击坐标(相对控件本身):”, “onTouch: x,” + event.getX() + “;y,” + event.getY());
Log.d(“父布局:点击坐标(相对上层布局):”, “onTouch: Left,” + v.getLeft() + “;Top,” + v.getTop() + “;Right,” + v.getRight()
+ “;Bottom,” + v.getBottom());
Log.d(“父布局:点击坐标(相对整个屏幕):”, “onTouch: X,” + event.getRawX() + “;Y,” + event.getRawY());
break;
case MotionEvent.ACTION_UP:
Log.d(“父布局:”, “onTouch: MotionEvent.ACTION_UP”);
Log.d(“父布局:点击坐标(相对控件本身):”, “onTouch: x,” + event.getX() + “;y,” + event.getY());
Log.d(“父布局:点击坐标(相对上层布局):”, “onTouch: Left,” + v.getLeft() + “;Top,” + v.getTop() + “;Right,” + v.getRight()
+ “;Bottom,” + v.getBottom());
Log.d(“父布局:点击坐标(相对整个屏幕):”, “onTouch: X,” + event.getRawX() + “;Y,” + event.getRawY());
break;
case MotionEvent.ACTION_MOVE:
Log.d(“父布局:”, “onTouch: MotionEvent.ACTION_MOVE”);
break;
case MotionEvent.ACTION_CANCEL:
Log.d(“父布局:”, “onTouch: MotionEvent.ACTION_CANCEL”);
break;
case MotionEvent.ACTION_OUTSIDE:
Log.d(“父布局:”, “onTouch: MotionEvent.ACTION_OUTSIDE”);
break;
case MotionEvent.ACTION_POINTER_DOWN:
Log.d(“父布局:”, “onTouch: MotionEvent.ACTION_POINTER_DOWN”);
break;
case MotionEvent.ACTION_POINTER_UP:
Log.d(“父布局:”, “onTouch: MotionEvent.ACTION_POINTER_UP”);
break;
default:
break;
}
return true;
}
});
}
}
activity_main.xml代码:

<?xml version=”1.0″ encoding=”utf-8″?>
<LinearLayout xmlns:android=”http://schemas.android.com/apk/res/android”
xmlns:app=”http://schemas.android.com/apk/res-auto”
xmlns:tools=”http://schemas.android.com/tools”
android:layout_width=”match_parent”
android:layout_height=”match_parent”
android:orientation=”vertical”
tools:context=”com.hfut.operationscrollpre.MainActivity”>

<LinearLayout
android:layout_marginLeft=”100px”
android:id=”@+id/test_layout”
android:orientation=”vertical”
android:gravity=”center”
android:layout_width=”match_parent”
android:layout_height=”match_parent”>

<Button
android:text=”我是测试按钮”
android:id=”@+id/test_button”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content” />
</LinearLayout>

</LinearLayout>
4.2 测试结果
(1)点击屏幕

%title插图%num

 

(2)滑动屏幕

%title插图%num

 

(3)点击按钮

%title插图%num

(4)滑动按钮

%title插图%num

分析:

getX() + getLeft()+父布局到屏幕左侧距离=getRawX()

getY()+getTop()+父布局到屏幕上边距离=getRawY()

从日志来看我们的父布局(id为test_layout)到屏幕上边距离是118px,实际上我们并没有设置marginTop属性值,这里我们千万别忘了其父布局上面还有一个存放ActionBar的FrameLayout所占据的空间,还有就是我们平时喜欢使用dp单位来表示margin属性值,而这里获取的坐标单位都是px,所以在测试的时候需要注意,有时候我们设置了margin值为100dp,显示的结果大小很可能不是100px,这之间有一个单位换算。可以使用:

public static int dp2px(Context context, float dipValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (dipValue * scale + 0.5f);
}
获取,首先获取像素密度,然后在换算成对应的像素值;假如我们把上面的设置改为:

<LinearLayout
android:layout_marginLeft=”100dp”
android:id=”@+id/test_layout”
android:orientation=”vertical”
android:gravity=”center”
android:layout_width=”match_parent”
android:layout_height=”match_parent”>

<Button
android:text=”我是测试按钮”
android:id=”@+id/test_button”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content” />
</LinearLayout>
再次点击子布局,日志如下:

%title插图%num

再看看MainActivity中打印的日志:

Log.d(TAG, “onCreate: “+dp2px(getApplicationContext(),100));//代码

08-18 10:39:58.096 9114-9114/? D/MainActivity: onCreate: 133 //日志
————————————————

ios如何解除dns被劫持_iOS强制ATS后,DNS劫持问题如何解决?

之前苹果强制app上传AppStore必须支持ATS,截至日期是2017年01月01日,但是由于各种原因,导致deadline延期.具体什么时候苹果会强制ATS,官方暂时还没有给出明确答复.

支持Https后,一般情况下只会给域名添加证书.导致app所有的http请求都会走域名,这样就会有DNS劫持的风险.无论wifi网络下,还是移动网络根据域名都会去DNS服务解析成ip,然后进行访问.由于国内网络环境的原因,都会有DNS劫持的情况,一般会在访问网页的时候,在页面上嵌入一段js代码,甚至有些情况会出现DNS解析失败.尤其是用户达到一定规模,各种网络状况都会出现.

这里用移动设备举例,如果是wifi网络,一般用户是不会修改网络设置的DNS. 很有可能用户网络环境由于种种原因,会造成无法访问http服务.ATS之前的解决方案是可以直接使用ip地址,一般app都会有这样一个逻辑,从服务器获取DNS Config,这里面一般配置了domain,ip,protocol,port等属性,App的请求可以根据DNS Config进行动态调整.但是支持ATS后天,苹果设置必须支持https.这样一旦使用域名的http服务,都会有可能遇到DNS劫持的情况.

由于IP不一定能够长期保持,所以一般不会给ip地址配证书.一旦苹果强制支持ATS,那么就存在DNS劫持的风险.

dig命令查询DNS解析,下面是解析百度的域名

dig www.baidu.com

; <<>> DiG 9.8.3-P1 <<>> www.baidu.com

;; global options: +cmd

;; Got answer:

;; ->>HEADER<

;; flags: qr rd ra; QUERY: 1, ANSWER: 3, AUTHORITY: 5, ADDITIONAL: 5

;; QUESTION SECTION:

;www.baidu.com.INA

;; ANSWER SECTION:

www.baidu.com.380INCNAMEwww.a.shifen.com.

www.a.shifen.com.134INA119.75.218.70

www.a.shifen.com.134INA119.75.217.109

;; AUTHORITY SECTION:

a.shifen.com.194INNSns3.a.shifen.com.

a.shifen.com.194INNSns5.a.shifen.com.

a.shifen.com.194INNSns4.a.shifen.com.

a.shifen.com.194INNSns2.a.shifen.com.

a.shifen.com.194INNSns1.a.shifen.com.

;; ADDITIONAL SECTION:

ns4.a.shifen.com.49INA115.239.210.176

ns2.a.shifen.com.580INA180.149.133.241

ns5.a.shifen.com.580INA119.75.222.17

ns1.a.shifen.com.580INA61.135.165.224

ns3.a.shifen.com.238INA61.135.162.215

;; Query time: 6 msec

;; SERVER: 172.17.16.3#53(172.17.16.3)

;; WHEN: Tue Feb 21 12:25:06 2017

;; MSG SIZE rcvd: 260

浏览器直接输入这两个ip:119.75.218.70,119.75.217.109,可以直接访问百度.如果前面加上https://119.75.218.70,进行访问,由于没有证书会显示不安全的链接.但是跳过之后还是能够正常访问.我用ios设备测试,底层调用https://ip可以正常访问.各种请求都没有问题.

如果正常网络环境下可以用所有的http服务尽量域名访问,每次启动app时可以向服务器获取dig出来的ip作为备用ip,一旦遇到DNS劫持的情况,虽然没有给ip配证书,但是底层可以使用ip正常访问.

django Paginator分页+Boostrap样式快速生成分页按钮

django Paginator分页+Boostrap样式快速生成分页按钮
django自带分页功能,方便我们快速调用,更多介绍见官方文档
1.导入
from django.core.paginator import Paginator
1
2.属性和方法
p = Paginator(object_list, per_page, orphans=0, allow_empty_first_page=True)
#构造Paginator对象
#object_list:要分页的对象,可以是列表,元组,或django中的QuerySet等
#per_page:每页*多的文章数
#orphans:当*后一页的文章数小于或等于该值时,这些文章将被自动合并到前一页
#allow_empty_first_page:是否允许*页为空
#属性
p.count #文章总数
p.num_pages  #总分页数
p.page_range #页码范围,返回一个列表,从1开始,如[1,2,3,4]
#方法
page = p.page(number) #返回指定页码的page对象,若不存在则抛出错误
#page对象的属性和方法
Page.object_list  #包含当前页的所有对象列表
Page.number       #当前页的页码,从1开始
Page.has_next()   #是否有下一页,若有返回True
Page.has_previous()  #是否有上一页,若有返回True
Page.has_other_pages()  #是否有下一页或上一页,若有返回True
Page.next_page_number() #返回下一页的页码
Page.previous_page_number() #返回上一页的页码
Page.start_index()  #返回当前页的*个对象在所有对象列表中的序号
Page.end_index()  #返回当前页的*后一个对象在所有对象列表中的序号
3.例子
views.py
def news(request,type):
articles = Article.objects.all().filter(category__name = type).order_by(‘-time’)  #导入的Article模型
p = Paginator(articles,10)   #分页,10篇文章一页
if p.num_pages <= 1:  #如果文章不足一页
article_list = articles  #直接返回所有文章
data = ”  #不需要分页按钮
else:
page = int(request.GET.get(‘page’,1))  #获取请求的文章页码,默认为*页
article_list = p.page(page) #返回指定页码的页面
left = []  # 当前页左边连续的页码号,初始值为空
right = []  # 当前页右边连续的页码号,初始值为空
left_has_more = False  # 标示第 1 页页码后是否需要显示省略号
right_has_more = False  # 标示*后一页页码前是否需要显示省略号
first = False   # 标示是否需要显示第 1 页的页码号。
        # 因为如果当前页左边的连续页码号中已经含有第 1 页的页码号,此时就无需再显示第 1 页的页码号,
        # 其它情况下*页的页码是始终需要显示的。
        # 初始值为 False
last = False  # 标示是否需要显示*后一页的页码号。
total_pages = p.num_pages
page_range = p.page_range
if page == 1:  #如果请求第1页
right = page_range[page:page+2]  #获取右边连续号码页
if right[-1] < total_pages – 1:    # 如果*右边的页码号比*后一页的页码号减去 1 还要小,
            # 说明*右边的页码号和*后一页的页码号之间还有其它页码,因此需要显示省略号,通过 right_has_more 来指示。
right_has_more = True
if right[-1] < total_pages:   # 如果*右边的页码号比*后一页的页码号小,说明当前页右边的连续页码号中不包含*后一页的页码
            # 所以需要显示*后一页的页码号,通过 last 来指示
last = True
elif page == total_pages:  #如果请求*后一页
left = page_range[(page-3) if (page-3) > 0 else 0:page-1]  #获取左边连续号码页
if left[0] > 2:
left_has_more = True  #如果*左边的号码比2还要大,说明其与*页之间还有其他页码,因此需要显示省略号,通过 left_has_more 来指示
if left[0] > 1: #如果*左边的页码比1要大,则要显示*页,否则*页已经被包含在其中
first = True
else:  #如果请求的页码既不是*页也不是*后一页
left = page_range[(page-3) if (page-3) > 0 else 0:page-1]   #获取左边连续号码页
right = page_range[page:page+2] #获取右边连续号码页
if left[0] > 2:
left_has_more = True
if left[0] > 1:
first = True
if right[-1] < total_pages – 1:
right_has_more = True
if right[-1] < total_pages:
last = True
data = {    #将数据包含在data字典中
‘left’:left,
‘right’:right,
‘left_has_more’:left_has_more,
‘right_has_more’:right_has_more,
‘first’:first,
‘last’:last,
‘total_pages’:total_pages,
‘page’:page
}
return render(request,’news.html’,context={
‘article_list’:article_list,’data’:data
})
html
{% if data %}
<ul id=”pages” class=”pagination pagination-sm pagination-xs”>
{% if data.first %}
<li><a href=”?page=1″>1</a></li>
{% endif %}
{% if data.left %}
{% if data.left_has_more %}
<li><span>…</span></li>
{% endif %}
{% for i in data.left %}
<li><a href=”?page={{i}}”>{{i}}</a></li>
{% endfor %}
{% endif %}
<li class=”active”><a href=”?page={{data.page}}”>{{data.page}}</a></li>
{% if data.right %}
{% for i in data.right %}
<li><a href=”?page={{i}}”>{{i}}</a></li>
{% endfor %}
{% if data.right_has_more %}
<li><span>…</span></li>
{% endif %}
{% endif %}
{% if data.last %}
<li><a href=”?page={{data.total_pages}}”>{{data.total_pages}}</a></li>
{% endif %}
</ul>
{% endif %}
友情链接: SITEMAP | 旋风加速器官网 | 旋风软件中心 | textarea | 黑洞加速器 | jiaohess | 老王加速器 | 烧饼哥加速器 | 小蓝鸟 | tiktok加速器 | 旋风加速度器 | 旋风加速 | quickq加速器 | 飞驰加速器 | 飞鸟加速器 | 狗急加速器 | hammer加速器 | trafficace | 原子加速器 | 葫芦加速器 | 麦旋风 | 油管加速器 | anycastly | INS加速器 | INS加速器免费版 | 免费vqn加速外网 | 旋风加速器 | 快橙加速器 | 啊哈加速器 | 迷雾通 | 优途加速器 | 海外播 | 坚果加速器 | 海外vqn加速 | 蘑菇加速器 | 毛豆加速器 | 接码平台 | 接码S | 西柚加速器 | 快柠檬加速器 | 黑洞加速 | falemon | 快橙加速器 | anycast加速器 | ibaidu | moneytreeblog | 坚果加速器 | 派币加速器 | 飞鸟加速器 | 毛豆APP | PIKPAK | 安卓vqn免费 | 一元机场加速器 | 一元机场 | 老王加速器 | 黑洞加速器 | 白石山 | 小牛加速器 | 黑洞加速 | 迷雾通官网 | 迷雾通 | 迷雾通加速器 | 十大免费加速神器 | 猎豹加速器 | 蚂蚁加速器 | 坚果加速器 | 黑洞加速 | 银河加速器 | 猎豹加速器 | 海鸥加速器 | 芒果加速器 | 小牛加速器 | 极光加速器 | 黑洞加速 | movabletype中文网 | 猎豹加速器官网 | 烧饼哥加速器官网 | 旋风加速器度器 | 哔咔漫画 | PicACG | 雷霆加速